propellor 2.17.2 → 3.0.0
raw patch · 102 files changed
+3061/−2550 lines, 102 filesdep +concurrent-outputPVP ok
version bump matches the API change (PVP)
Dependencies added: concurrent-output
API changes (from Hackage documentation)
- Propellor.Engine: ensureProperties :: [Property NoInfo] -> Propellor Result
- Propellor.Engine: ensureProperty :: Property NoInfo -> Propellor Result
- Propellor.Info: addHostInfo :: IsInfo v => Host -> v -> Host
- Propellor.Info: findAlias :: [Host] -> HostName -> Maybe Host
- Propellor.Info: os :: System -> Property HasInfo
- Propellor.PropAccum: addProp :: (PropAccum h, IsProp p) => h -> p -> h
- Propellor.PropAccum: addPropFront :: (PropAccum h, IsProp p) => h -> p -> h
- Propellor.PropAccum: class PropAccum h
- Propellor.PropAccum: getProperties :: PropAccum h => h -> [Property HasInfo]
- Propellor.PropAccum: instance Propellor.PropAccum.PropAccum Propellor.Types.Host
- Propellor.PropAccum: propagateContainer :: (PropAccum container) => String -> container -> Property HasInfo -> Property HasInfo
- Propellor.Property.Chroot: instance Propellor.PropAccum.PropAccum Propellor.Property.Chroot.Chroot
- Propellor.Property.Conductor: instance Propellor.Property.Conductor.Conductable Propellor.Types.Host
- Propellor.Property.Conductor: instance Propellor.Property.Conductor.Conductable [Propellor.Types.Host]
- Propellor.Property.Docker: instance Propellor.PropAccum.PropAccum Propellor.Property.Docker.Container
- Propellor.Property.List: PropList :: [Property HasInfo] -> PropList
- Propellor.Property.List: class PropertyList l
- Propellor.Property.List: data PropList
- Propellor.Property.List: instance Propellor.PropAccum.PropAccum Propellor.Property.List.PropList
- Propellor.Property.List: instance Propellor.Property.List.PropertyList Propellor.Property.List.PropList
- Propellor.Property.List: instance Propellor.Property.List.PropertyList [Propellor.Types.Property Propellor.Types.HasInfo]
- Propellor.Property.List: instance Propellor.Property.List.PropertyList [Propellor.Types.Property Propellor.Types.NoInfo]
- Propellor.Property.List: instance Propellor.Property.List.PropertyList [Propellor.Types.RevertableProperty Propellor.Types.HasInfo]
- Propellor.Property.List: instance Propellor.Property.List.PropertyList [Propellor.Types.RevertableProperty Propellor.Types.NoInfo]
- Propellor.Property.SiteSpecific.GitAnnexBuilder: androidContainer :: (IsProp (Property (CInfo NoInfo i)), Combines (Property NoInfo) (Property i)) => MachineName -> Property i -> FilePath -> Container
- Propellor.Property.SiteSpecific.JoeySites: kitenetHttps :: Property HasInfo
- Propellor.Property.SiteSpecific.JoeySites: mainhttpscert :: Bool -> ConfigFile
- Propellor.Property.Systemd: instance Propellor.PropAccum.PropAccum Propellor.Property.Systemd.Container
- Propellor.Types: EndAction :: Desc -> (Result -> Propellor Result) -> EndAction
- Propellor.Types: class IsProp p
- Propellor.Types: class MkRevertableProperty i1 i2
- Propellor.Types: data EndAction
- Propellor.Types: data HasInfo
- Propellor.Types: data NoInfo
- Propellor.Types: getDesc :: IsProp p => p -> Desc
- Propellor.Types: getInfoRecursive :: IsProp p => p -> Info
- Propellor.Types: ignoreInfo :: Property i -> Property NoInfo
- Propellor.Types: infoProperty :: Desc -> Propellor Result -> Info -> [Property i] -> Property HasInfo
- Propellor.Types: instance Control.Monad.Catch.MonadCatch Propellor.Types.Propellor
- Propellor.Types: instance Control.Monad.Catch.MonadMask Propellor.Types.Propellor
- Propellor.Types: instance Control.Monad.Catch.MonadThrow Propellor.Types.Propellor
- Propellor.Types: instance Control.Monad.IO.Class.MonadIO Propellor.Types.Propellor
- Propellor.Types: instance Control.Monad.Reader.Class.MonadReader Propellor.Types.Host Propellor.Types.Propellor
- Propellor.Types: instance Control.Monad.Writer.Class.MonadWriter [Propellor.Types.EndAction] Propellor.Types.Propellor
- Propellor.Types: instance GHC.Base.Applicative Propellor.Types.Propellor
- Propellor.Types: instance GHC.Base.Functor Propellor.Types.Propellor
- Propellor.Types: instance GHC.Base.Monad Propellor.Types.Propellor
- Propellor.Types: instance GHC.Base.Monoid (Propellor.Types.Propellor Propellor.Types.Result.Result)
- Propellor.Types: instance GHC.Show.Show (Propellor.Types.Property i)
- Propellor.Types: instance GHC.Show.Show (Propellor.Types.RevertableProperty i)
- Propellor.Types: instance GHC.Show.Show Propellor.Types.Host
- Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.Property Propellor.Types.HasInfo) (Propellor.Types.Property Propellor.Types.HasInfo)
- Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.Property Propellor.Types.HasInfo) (Propellor.Types.Property Propellor.Types.NoInfo)
- Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.Property Propellor.Types.HasInfo) (Propellor.Types.RevertableProperty Propellor.Types.HasInfo)
- Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.Property Propellor.Types.HasInfo) (Propellor.Types.RevertableProperty Propellor.Types.NoInfo)
- Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.Property Propellor.Types.NoInfo) (Propellor.Types.Property Propellor.Types.HasInfo)
- Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.Property Propellor.Types.NoInfo) (Propellor.Types.Property Propellor.Types.NoInfo)
- Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.Property Propellor.Types.NoInfo) (Propellor.Types.RevertableProperty Propellor.Types.HasInfo)
- Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.Property Propellor.Types.NoInfo) (Propellor.Types.RevertableProperty Propellor.Types.NoInfo)
- Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.RevertableProperty Propellor.Types.HasInfo) (Propellor.Types.Property Propellor.Types.HasInfo)
- Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.RevertableProperty Propellor.Types.HasInfo) (Propellor.Types.Property Propellor.Types.NoInfo)
- Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.RevertableProperty Propellor.Types.HasInfo) (Propellor.Types.RevertableProperty Propellor.Types.HasInfo)
- Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.RevertableProperty Propellor.Types.HasInfo) (Propellor.Types.RevertableProperty Propellor.Types.NoInfo)
- Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.RevertableProperty Propellor.Types.NoInfo) (Propellor.Types.Property Propellor.Types.HasInfo)
- Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.RevertableProperty Propellor.Types.NoInfo) (Propellor.Types.Property Propellor.Types.NoInfo)
- Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.RevertableProperty Propellor.Types.NoInfo) (Propellor.Types.RevertableProperty Propellor.Types.HasInfo)
- Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.RevertableProperty Propellor.Types.NoInfo) (Propellor.Types.RevertableProperty Propellor.Types.NoInfo)
- Propellor.Types: instance Propellor.Types.IsProp (Propellor.Types.Property Propellor.Types.HasInfo)
- Propellor.Types: instance Propellor.Types.IsProp (Propellor.Types.Property Propellor.Types.NoInfo)
- Propellor.Types: instance Propellor.Types.IsProp (Propellor.Types.RevertableProperty Propellor.Types.HasInfo)
- Propellor.Types: instance Propellor.Types.IsProp (Propellor.Types.RevertableProperty Propellor.Types.NoInfo)
- Propellor.Types: instance Propellor.Types.LiftPropellor GHC.Types.IO
- Propellor.Types: instance Propellor.Types.LiftPropellor Propellor.Types.Propellor
- Propellor.Types: instance Propellor.Types.MkRevertableProperty Propellor.Types.HasInfo Propellor.Types.HasInfo
- Propellor.Types: instance Propellor.Types.MkRevertableProperty Propellor.Types.HasInfo Propellor.Types.NoInfo
- Propellor.Types: instance Propellor.Types.MkRevertableProperty Propellor.Types.NoInfo Propellor.Types.HasInfo
- Propellor.Types: instance Propellor.Types.MkRevertableProperty Propellor.Types.NoInfo Propellor.Types.NoInfo
- Propellor.Types: propertyChildren :: Property i -> [Property i]
- Propellor.Types: propertyDesc :: Property i -> Desc
- Propellor.Types: propertyInfo :: Property i -> Info
- Propellor.Types: propertySatisfy :: Property i -> Propellor Result
- Propellor.Types: setDesc :: IsProp p => p -> Desc -> p
- Propellor.Types: simpleProperty :: Desc -> Propellor Result -> [Property NoInfo] -> Property NoInfo
- Propellor.Types: toProp :: IsProp p => p -> Property HasInfo
- Propellor.Types.Info: getInfo :: IsInfo v => Info -> v
+ Paths_propellor: getBinDir :: IO FilePath
+ Paths_propellor: getDataDir :: IO FilePath
+ Paths_propellor: getDataFileName :: FilePath -> IO FilePath
+ Paths_propellor: getLibDir :: IO FilePath
+ Paths_propellor: getLibexecDir :: IO FilePath
+ Paths_propellor: getSysconfDir :: IO FilePath
+ Paths_propellor: version :: Version
+ Propellor: Property :: metatypes -> Desc -> (Propellor Result) -> Info -> [ChildProperty] -> Property metatypes
+ Propellor.Container: class IsContainer c
+ Propellor.Container: containerInfo :: IsContainer c => c -> Info
+ Propellor.Container: containerProperties :: IsContainer c => c -> [ChildProperty]
+ Propellor.Container: containerProps :: IsContainer c => c -> Props UnixLike
+ Propellor.Container: instance Propellor.Container.IsContainer Propellor.Types.Core.Host
+ Propellor.Container: propagateContainer :: (IncludesInfo metatypes ~ True, IsContainer c) => String -> c -> Property metatypes -> Property metatypes
+ Propellor.Container: setContainerProperties :: IsContainer c => c -> [ChildProperty] -> c
+ Propellor.Container: setContainerProps :: IsContainer c => c -> Props metatypes -> c
+ Propellor.DotDir: buildSystem :: IO String
+ Propellor.DotDir: checkRepoUpToDate :: IO ()
+ Propellor.DotDir: distdir :: FilePath
+ Propellor.DotDir: disthead :: FilePath
+ Propellor.DotDir: distrepo :: FilePath
+ Propellor.DotDir: dotPropellor :: IO FilePath
+ Propellor.DotDir: explainManualSetupGpgKey :: IO ()
+ Propellor.DotDir: fetchUpstreamBranch :: FilePath -> IO Bool
+ Propellor.DotDir: fullClone :: IO Result
+ Propellor.DotDir: interactiveInit :: IO ()
+ Propellor.DotDir: makeGpgKey :: IO ()
+ Propellor.DotDir: minimalConfig :: IO Result
+ Propellor.DotDir: netrepo :: String
+ Propellor.DotDir: prompt :: String -> [(String, IO ())] -> IO ()
+ Propellor.DotDir: propellorAddKey :: String -> IO ()
+ Propellor.DotDir: say :: String -> IO ()
+ Propellor.DotDir: sayLn :: String -> IO ()
+ Propellor.DotDir: section :: IO ()
+ Propellor.DotDir: setup :: IO ()
+ Propellor.DotDir: setupGpgKey :: IO ()
+ Propellor.DotDir: setupUpstreamMaster :: String -> IO ()
+ Propellor.DotDir: upstreambranch :: String
+ Propellor.DotDir: warnoutofdate :: Bool -> IO ()
+ Propellor.DotDir: welcomeBanner :: IO ()
+ Propellor.Engine: ensureChildProperties :: [ChildProperty] -> Propellor Result
+ Propellor.EnsureProperty: OuterMetaTypesWitness :: (MetaTypes metatypes) -> OuterMetaTypesWitness metatypes
+ Propellor.EnsureProperty: ensureProperty :: (Cannot_ensureProperty_WithInfo inner ~ True, (Targets inner `NotSuperset` Targets outer) ~ CanCombine) => OuterMetaTypesWitness outer -> Property (MetaTypes inner) -> Propellor Result
+ Propellor.EnsureProperty: newtype OuterMetaTypesWitness metatypes
+ Propellor.EnsureProperty: property' :: SingI metatypes => Desc -> (OuterMetaTypesWitness metatypes -> Propellor Result) -> Property (MetaTypes metatypes)
+ Propellor.Info: addInfoProperty :: (IncludesInfo metatypes ~ True) => Property metatypes -> Info -> Property metatypes
+ Propellor.Info: osBuntish :: Release -> Architecture -> Property (HasInfo + Buntish)
+ Propellor.Info: osDebian :: DebianSuite -> Architecture -> Property (HasInfo + Debian)
+ Propellor.Info: osFreeBSD :: FreeBSDRelease -> Architecture -> Property (HasInfo + FreeBSD)
+ Propellor.Info: setInfoProperty :: (MetaTypes metatypes' ~ (+) HasInfo metatypes, SingI metatypes') => Property metatypes -> Info -> Property (MetaTypes metatypes')
+ Propellor.PropAccum: Props :: [ChildProperty] -> Props metatypes
+ Propellor.PropAccum: data Props metatypes
+ Propellor.PropAccum: props :: Props UnixLike
+ Propellor.Property: data OuterMetaTypesWitness metatypes
+ Propellor.Property: pickOS :: (SingKind (KProxy :: KProxy ka), SingKind (KProxy :: KProxy kb), DemoteRep (KProxy :: KProxy ka) ~ [MetaType], DemoteRep (KProxy :: KProxy kb) ~ [MetaType], SingI c) => Property (MetaTypes (a :: ka)) -> Property (MetaTypes (b :: kb)) -> Property (MetaTypes c)
+ Propellor.Property: property' :: SingI metatypes => Desc -> (OuterMetaTypesWitness metatypes -> Propellor Result) -> Property (MetaTypes metatypes)
+ Propellor.Property: unsupportedOS' :: Propellor Result
+ Propellor.Property.Apt.PPA: AptKeyId :: String -> String -> String -> AptKeyId
+ Propellor.Property.Apt.PPA: AptRepositoryPPA :: PPA -> AptRepository
+ Propellor.Property.Apt.PPA: AptRepositorySource :: AptSource -> AptRepository
+ Propellor.Property.Apt.PPA: AptSource :: Url -> String -> [String] -> AptSource
+ Propellor.Property.Apt.PPA: PPA :: String -> String -> PPA
+ Propellor.Property.Apt.PPA: [akiId] :: AptKeyId -> String
+ Propellor.Property.Apt.PPA: [akiName] :: AptKeyId -> String
+ Propellor.Property.Apt.PPA: [akiServer] :: AptKeyId -> String
+ Propellor.Property.Apt.PPA: [asComponents] :: AptSource -> [String]
+ Propellor.Property.Apt.PPA: [asSuite] :: AptSource -> String
+ Propellor.Property.Apt.PPA: [asURL] :: AptSource -> Url
+ Propellor.Property.Apt.PPA: [ppaAccount] :: PPA -> String
+ Propellor.Property.Apt.PPA: [ppaArchive] :: PPA -> String
+ Propellor.Property.Apt.PPA: addKeyId :: AptKeyId -> Property DebianLike
+ Propellor.Property.Apt.PPA: addPpa :: PPA -> Property DebianLike
+ Propellor.Property.Apt.PPA: addRepository :: AptRepository -> Property DebianLike
+ Propellor.Property.Apt.PPA: data AptKeyId
+ Propellor.Property.Apt.PPA: data AptRepository
+ Propellor.Property.Apt.PPA: data AptSource
+ Propellor.Property.Apt.PPA: data PPA
+ Propellor.Property.Apt.PPA: installed :: Property DebianLike
+ Propellor.Property.Apt.PPA: instance Data.String.IsString Propellor.Property.Apt.PPA.AptSource
+ Propellor.Property.Apt.PPA: instance Data.String.IsString Propellor.Property.Apt.PPA.PPA
+ Propellor.Property.Apt.PPA: instance GHC.Classes.Eq Propellor.Property.Apt.PPA.AptKeyId
+ Propellor.Property.Apt.PPA: instance GHC.Classes.Eq Propellor.Property.Apt.PPA.AptSource
+ Propellor.Property.Apt.PPA: instance GHC.Classes.Eq Propellor.Property.Apt.PPA.PPA
+ Propellor.Property.Apt.PPA: instance GHC.Classes.Ord Propellor.Property.Apt.PPA.AptKeyId
+ Propellor.Property.Apt.PPA: instance GHC.Classes.Ord Propellor.Property.Apt.PPA.AptSource
+ Propellor.Property.Apt.PPA: instance GHC.Classes.Ord Propellor.Property.Apt.PPA.PPA
+ Propellor.Property.Apt.PPA: instance GHC.Show.Show Propellor.Property.Apt.PPA.AptKeyId
+ Propellor.Property.Apt.PPA: instance GHC.Show.Show Propellor.Property.Apt.PPA.AptSource
+ Propellor.Property.Apt.PPA: instance GHC.Show.Show Propellor.Property.Apt.PPA.PPA
+ Propellor.Property.Chroot: instance Propellor.Container.IsContainer Propellor.Property.Chroot.Chroot
+ Propellor.Property.Conductor: instance Propellor.Property.Conductor.Conductable Propellor.Types.Core.Host
+ Propellor.Property.Conductor: instance Propellor.Property.Conductor.Conductable [Propellor.Types.Core.Host]
+ Propellor.Property.Docker: instance Propellor.Container.IsContainer Propellor.Property.Docker.Container
+ Propellor.Property.List: data Props metatypes
+ Propellor.Property.List: toProps :: [Property (MetaTypes metatypes)] -> Props (MetaTypes metatypes)
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: androidAutoBuilderContainer' :: MachineName -> Property DebianLike -> FilePath -> Times -> TimeOut -> Container
+ Propellor.Property.SiteSpecific.JoeySites: letos :: AgreeTOS
+ Propellor.Property.Systemd: debContainer :: MachineName -> Props metatypes -> Container
+ Propellor.Property.Systemd: instance Propellor.Container.IsContainer Propellor.Property.Systemd.Container
+ Propellor.Property.ZFS.Properties: type ZFSOS = Linux + FreeBSD
+ Propellor.Types: Property :: metatypes -> Desc -> (Propellor Result) -> Info -> [ChildProperty] -> Property metatypes
+ Propellor.Types: class TightenTargets p
+ Propellor.Types: instance GHC.Show.Show (Propellor.Types.Property metatypes)
+ Propellor.Types: instance GHC.Show.Show (Propellor.Types.RevertableProperty setupmetatypes undometatypes)
+ Propellor.Types: instance Propellor.Types.Core.IsProp (Propellor.Types.Property metatypes)
+ Propellor.Types: instance Propellor.Types.Core.IsProp (Propellor.Types.RevertableProperty setupmetatypes undometatypes)
+ Propellor.Types: instance Propellor.Types.TightenTargets Propellor.Types.Property
+ Propellor.Types: instance forall (k :: BOX) (k1 :: BOX) (x :: [k]) (x' :: [k1]) (y :: [k]) (y' :: [k1]). (Propellor.Types.MetaTypes.CheckCombinable x y ~ 'Propellor.Types.MetaTypes.CanCombine, Propellor.Types.MetaTypes.CheckCombinable x' y' ~ 'Propellor.Types.MetaTypes.CanCombine, Propellor.Types.Singletons.SingI (Propellor.Types.MetaTypes.Combine x y), Propellor.Types.Singletons.SingI (Propellor.Types.MetaTypes.Combine x' y')) => Propellor.Types.Combines (Propellor.Types.RevertableProperty (Propellor.Types.MetaTypes.MetaTypes x) (Propellor.Types.MetaTypes.MetaTypes x')) (Propellor.Types.RevertableProperty (Propellor.Types.MetaTypes.MetaTypes y) (Propellor.Types.MetaTypes.MetaTypes y'))
+ Propellor.Types: instance forall (k :: BOX) (k1 :: BOX) (x :: [k]) (x' :: k1) (y :: [k]). (Propellor.Types.MetaTypes.CheckCombinable x y ~ 'Propellor.Types.MetaTypes.CanCombine, Propellor.Types.Singletons.SingI (Propellor.Types.MetaTypes.Combine x y)) => Propellor.Types.Combines (Propellor.Types.RevertableProperty (Propellor.Types.MetaTypes.MetaTypes x) (Propellor.Types.MetaTypes.MetaTypes x')) (Propellor.Types.Property (Propellor.Types.MetaTypes.MetaTypes y))
+ Propellor.Types: instance forall (k :: BOX) (k1 :: BOX) (x :: [k]) (y :: [k]) (y' :: k1). (Propellor.Types.MetaTypes.CheckCombinable x y ~ 'Propellor.Types.MetaTypes.CanCombine, Propellor.Types.Singletons.SingI (Propellor.Types.MetaTypes.Combine x y)) => Propellor.Types.Combines (Propellor.Types.Property (Propellor.Types.MetaTypes.MetaTypes x)) (Propellor.Types.RevertableProperty (Propellor.Types.MetaTypes.MetaTypes y) (Propellor.Types.MetaTypes.MetaTypes y'))
+ Propellor.Types: instance forall (k :: BOX) (x :: [k]) (y :: [k]). (Propellor.Types.MetaTypes.CheckCombinable x y ~ 'Propellor.Types.MetaTypes.CanCombine, Propellor.Types.Singletons.SingI (Propellor.Types.MetaTypes.Combine x y)) => Propellor.Types.Combines (Propellor.Types.Property (Propellor.Types.MetaTypes.MetaTypes x)) (Propellor.Types.Property (Propellor.Types.MetaTypes.MetaTypes y))
+ Propellor.Types: property :: SingI metatypes => Desc -> Propellor Result -> Property (MetaTypes metatypes)
+ Propellor.Types: tightenTargets :: (TightenTargets p, (Targets untightened `NotSuperset` Targets tightened) ~ CanCombine, (NonTargets tightened `NotSuperset` NonTargets untightened) ~ CanCombine, SingI tightened) => p (MetaTypes untightened) -> p (MetaTypes tightened)
+ Propellor.Types: type Buntish = MetaTypes '[Targeting OSBuntish]
+ Propellor.Types: type Debian = MetaTypes '[Targeting OSDebian]
+ Propellor.Types: type DebianLike = MetaTypes '[Targeting OSDebian, Targeting OSBuntish]
+ Propellor.Types: type FreeBSD = MetaTypes '[Targeting OSFreeBSD]
+ Propellor.Types: type HasInfo = MetaTypes '[WithInfo]
+ Propellor.Types: type Linux = MetaTypes '[Targeting OSDebian, Targeting OSBuntish]
+ Propellor.Types: type UnixLike = MetaTypes '[Targeting OSDebian, Targeting OSBuntish, Targeting OSFreeBSD]
+ Propellor.Types.CmdLine: Init :: CmdLine
+ Propellor.Types.Core: ChildProperty :: Desc -> (Propellor Result) -> Info -> [ChildProperty] -> ChildProperty
+ Propellor.Types.Core: EndAction :: Desc -> (Result -> Propellor Result) -> EndAction
+ Propellor.Types.Core: Host :: HostName -> [ChildProperty] -> Info -> Host
+ Propellor.Types.Core: Propellor :: RWST Host [EndAction] () IO p -> Propellor p
+ Propellor.Types.Core: Props :: [ChildProperty] -> Props metatypes
+ Propellor.Types.Core: [hostInfo] :: Host -> Info
+ Propellor.Types.Core: [hostName] :: Host -> HostName
+ Propellor.Types.Core: [hostProperties] :: Host -> [ChildProperty]
+ Propellor.Types.Core: [runWithHost] :: Propellor p -> RWST Host [EndAction] () IO p
+ Propellor.Types.Core: addChildren :: IsProp p => p -> [ChildProperty] -> p
+ Propellor.Types.Core: class IsProp p
+ Propellor.Types.Core: class LiftPropellor m
+ Propellor.Types.Core: data ChildProperty
+ Propellor.Types.Core: data EndAction
+ Propellor.Types.Core: data Host
+ Propellor.Types.Core: data Props metatypes
+ Propellor.Types.Core: getChildren :: IsProp p => p -> [ChildProperty]
+ Propellor.Types.Core: getDesc :: IsProp p => p -> Desc
+ Propellor.Types.Core: getInfo :: IsProp p => p -> Info
+ Propellor.Types.Core: getInfoRecursive :: IsProp p => p -> Info
+ Propellor.Types.Core: getSatisfy :: IsProp p => p -> Propellor Result
+ Propellor.Types.Core: instance Control.Monad.Catch.MonadCatch Propellor.Types.Core.Propellor
+ Propellor.Types.Core: instance Control.Monad.Catch.MonadMask Propellor.Types.Core.Propellor
+ Propellor.Types.Core: instance Control.Monad.Catch.MonadThrow Propellor.Types.Core.Propellor
+ Propellor.Types.Core: instance Control.Monad.IO.Class.MonadIO Propellor.Types.Core.Propellor
+ Propellor.Types.Core: instance Control.Monad.Reader.Class.MonadReader Propellor.Types.Core.Host Propellor.Types.Core.Propellor
+ Propellor.Types.Core: instance Control.Monad.Writer.Class.MonadWriter [Propellor.Types.Core.EndAction] Propellor.Types.Core.Propellor
+ Propellor.Types.Core: instance GHC.Base.Applicative Propellor.Types.Core.Propellor
+ Propellor.Types.Core: instance GHC.Base.Functor Propellor.Types.Core.Propellor
+ Propellor.Types.Core: instance GHC.Base.Monad Propellor.Types.Core.Propellor
+ Propellor.Types.Core: instance GHC.Base.Monoid (Propellor.Types.Core.Propellor Propellor.Types.Result.Result)
+ Propellor.Types.Core: instance GHC.Show.Show Propellor.Types.Core.ChildProperty
+ Propellor.Types.Core: instance GHC.Show.Show Propellor.Types.Core.Host
+ Propellor.Types.Core: instance Propellor.Types.Core.IsProp Propellor.Types.Core.ChildProperty
+ Propellor.Types.Core: instance Propellor.Types.Core.LiftPropellor GHC.Types.IO
+ Propellor.Types.Core: instance Propellor.Types.Core.LiftPropellor Propellor.Types.Core.Propellor
+ Propellor.Types.Core: liftPropellor :: LiftPropellor m => m a -> Propellor a
+ Propellor.Types.Core: newtype Propellor p
+ Propellor.Types.Core: setDesc :: IsProp p => p -> Desc -> p
+ Propellor.Types.Core: toChildProperty :: IsProp p => p -> ChildProperty
+ Propellor.Types.Core: type Desc = String
+ Propellor.Types.Info: fromInfo :: IsInfo v => Info -> v
+ Propellor.Types.Info: toInfo :: IsInfo v => v -> Info
+ Propellor.Types.MetaTypes: CanCombine :: CheckCombine
+ Propellor.Types.MetaTypes: CannotCombineTargets :: CheckCombine
+ Propellor.Types.MetaTypes: Targeting :: TargetOS -> MetaType
+ Propellor.Types.MetaTypes: WithInfo :: MetaType
+ Propellor.Types.MetaTypes: class SingI t
+ Propellor.Types.MetaTypes: data CheckCombine
+ Propellor.Types.MetaTypes: data MetaType
+ Propellor.Types.MetaTypes: instance GHC.Classes.Eq Propellor.Types.MetaTypes.MetaType
+ Propellor.Types.MetaTypes: instance GHC.Classes.Ord Propellor.Types.MetaTypes.MetaType
+ Propellor.Types.MetaTypes: instance GHC.Show.Show Propellor.Types.MetaTypes.MetaType
+ Propellor.Types.MetaTypes: instance Propellor.Types.Singletons.SingI 'Propellor.Types.MetaTypes.WithInfo
+ Propellor.Types.MetaTypes: instance Propellor.Types.Singletons.SingI ('Propellor.Types.MetaTypes.Targeting 'Propellor.Types.OS.OSBuntish)
+ Propellor.Types.MetaTypes: instance Propellor.Types.Singletons.SingI ('Propellor.Types.MetaTypes.Targeting 'Propellor.Types.OS.OSDebian)
+ Propellor.Types.MetaTypes: instance Propellor.Types.Singletons.SingI ('Propellor.Types.MetaTypes.Targeting 'Propellor.Types.OS.OSFreeBSD)
+ Propellor.Types.MetaTypes: instance Propellor.Types.Singletons.SingKind 'Data.Proxy.KProxy
+ Propellor.Types.MetaTypes: sing :: SingI t => Sing t
+ Propellor.Types.MetaTypes: type Buntish = MetaTypes '[Targeting OSBuntish]
+ Propellor.Types.MetaTypes: type Debian = MetaTypes '[Targeting OSDebian]
+ Propellor.Types.MetaTypes: type DebianLike = MetaTypes '[Targeting OSDebian, Targeting OSBuntish]
+ Propellor.Types.MetaTypes: type FreeBSD = MetaTypes '[Targeting OSFreeBSD]
+ Propellor.Types.MetaTypes: type HasInfo = MetaTypes '[WithInfo]
+ Propellor.Types.MetaTypes: type Linux = MetaTypes '[Targeting OSDebian, Targeting OSBuntish]
+ Propellor.Types.MetaTypes: type MetaTypes = Sing
+ Propellor.Types.MetaTypes: type UnixLike = MetaTypes '[Targeting OSDebian, Targeting OSBuntish, Targeting OSFreeBSD]
+ Propellor.Types.OS: OSBuntish :: TargetOS
+ Propellor.Types.OS: OSDebian :: TargetOS
+ Propellor.Types.OS: OSFreeBSD :: TargetOS
+ Propellor.Types.OS: data TargetOS
+ Propellor.Types.OS: instance GHC.Classes.Eq Propellor.Types.OS.TargetOS
+ Propellor.Types.OS: instance GHC.Classes.Ord Propellor.Types.OS.TargetOS
+ Propellor.Types.OS: instance GHC.Show.Show Propellor.Types.OS.TargetOS
+ Propellor.Types.OS: systemToTargetOS :: System -> TargetOS
+ Propellor.Types.ResultCheck: instance Propellor.Types.TightenTargets Propellor.Types.ResultCheck.UncheckedProperty
+ Propellor.Types.Singletons: KProxy :: KProxy t
+ Propellor.Types.Singletons: class SingI t
+ Propellor.Types.Singletons: class (kparam ~ KProxy) => SingKind (kparam :: KProxy k) where type family DemoteRep kparam :: *
+ Propellor.Types.Singletons: data KProxy t :: * -> *
+ Propellor.Types.Singletons: fromSing :: SingKind kparam => Sing (a :: k) -> DemoteRep kparam
+ Propellor.Types.Singletons: instance Propellor.Types.Singletons.SingI 'GHC.Types.False
+ Propellor.Types.Singletons: instance Propellor.Types.Singletons.SingI 'GHC.Types.True
+ Propellor.Types.Singletons: instance Propellor.Types.Singletons.SingI '[]
+ Propellor.Types.Singletons: instance Propellor.Types.Singletons.SingKind 'Data.Proxy.KProxy
+ Propellor.Types.Singletons: instance Propellor.Types.Singletons.SingKind 'Data.Proxy.KProxy => Propellor.Types.Singletons.SingKind 'Data.Proxy.KProxy
+ Propellor.Types.Singletons: instance forall (k :: BOX) (x :: k) (xs :: [k]). (Propellor.Types.Singletons.SingI x, Propellor.Types.Singletons.SingI xs) => Propellor.Types.Singletons.SingI (x : xs)
+ Propellor.Types.Singletons: sing :: SingI t => Sing t
- Propellor: (!) :: IsProp (RevertableProperty i) => PropAccum h => h -> RevertableProperty i -> h
+ Propellor: (!) :: (CheckCombinable x z ~ CanCombine) => Props (MetaTypes x) -> RevertableProperty (MetaTypes y) (MetaTypes z) -> Props (MetaTypes (Combine x z))
- Propellor: (&) :: (PropAccum h, IsProp p) => h -> p -> h
+ Propellor: (&) :: (IsProp p, MetaTypes y ~ GetMetaTypes p, CheckCombinable x y ~ CanCombine) => Props (MetaTypes x) -> p -> Props (MetaTypes (Combine x y))
- Propellor: Host :: HostName -> [Property HasInfo] -> Info -> Host
+ Propellor: Host :: HostName -> [ChildProperty] -> Info -> Host
- Propellor: RevertableProperty :: Property i -> Property i -> RevertableProperty i
+ Propellor: RevertableProperty :: Property setupmetatypes -> Property undometatypes -> RevertableProperty setupmetatypes undometatypes
- Propellor: [hostProperties] :: Host -> [Property HasInfo]
+ Propellor: [hostProperties] :: Host -> [ChildProperty]
- Propellor: [setupRevertableProperty] :: RevertableProperty i -> Property i
+ Propellor: [setupRevertableProperty] :: RevertableProperty setupmetatypes undometatypes -> Property setupmetatypes
- Propellor: [undoRevertableProperty] :: RevertableProperty i -> Property i
+ Propellor: [undoRevertableProperty] :: RevertableProperty setupmetatypes undometatypes -> Property undometatypes
- Propellor: data Property i
+ Propellor: data Property metatypes
- Propellor: data RevertableProperty i
+ Propellor: data RevertableProperty setupmetatypes undometatypes
- Propellor: host :: HostName -> Host
+ Propellor: host :: HostName -> Props metatypes -> Host
- Propellor.Info: addDNS :: Record -> Property HasInfo
+ Propellor.Info: addDNS :: Record -> Property (HasInfo + UnixLike)
- Propellor.Info: alias :: Domain -> Property HasInfo
+ Propellor.Info: alias :: Domain -> Property (HasInfo + UnixLike)
- Propellor.Info: ipv4 :: String -> Property HasInfo
+ Propellor.Info: ipv4 :: String -> Property (HasInfo + UnixLike)
- Propellor.Info: ipv6 :: String -> Property HasInfo
+ Propellor.Info: ipv6 :: String -> Property (HasInfo + UnixLike)
- Propellor.Info: pureInfoProperty :: (IsInfo v) => Desc -> v -> Property HasInfo
+ Propellor.Info: pureInfoProperty :: (IsInfo v) => Desc -> v -> Property (HasInfo + UnixLike)
- Propellor.Info: pureInfoProperty' :: Desc -> Info -> Property HasInfo
+ Propellor.Info: pureInfoProperty' :: Desc -> Info -> Property (HasInfo + UnixLike)
- Propellor.PrivData: addPrivData :: (PrivDataField, Maybe PrivDataSourceDesc, HostContext) -> Property HasInfo
+ Propellor.PrivData: addPrivData :: (PrivDataField, Maybe PrivDataSourceDesc, HostContext) -> Property (HasInfo + UnixLike)
- Propellor.PrivData: withPrivData :: (IsContext c, IsPrivDataSource s, IsProp (Property i)) => s -> c -> (((PrivData -> Propellor Result) -> Propellor Result) -> Property i) -> Property HasInfo
+ Propellor.PrivData: withPrivData :: (IsContext c, IsPrivDataSource s, IncludesInfo metatypes ~ True) => s -> c -> (((PrivData -> Propellor Result) -> Propellor Result) -> Property metatypes) -> Property metatypes
- Propellor.PrivData: withSomePrivData :: (IsContext c, IsPrivDataSource s, IsProp (Property i)) => [s] -> c -> ((((PrivDataField, PrivData) -> Propellor Result) -> Propellor Result) -> Property i) -> Property HasInfo
+ Propellor.PrivData: withSomePrivData :: (IsContext c, IsPrivDataSource s, IncludesInfo metatypes ~ True) => [s] -> c -> ((((PrivDataField, PrivData) -> Propellor Result) -> Propellor Result) -> Property metatypes) -> Property metatypes
- Propellor.PropAccum: (!) :: IsProp (RevertableProperty i) => PropAccum h => h -> RevertableProperty i -> h
+ Propellor.PropAccum: (!) :: (CheckCombinable x z ~ CanCombine) => Props (MetaTypes x) -> RevertableProperty (MetaTypes y) (MetaTypes z) -> Props (MetaTypes (Combine x z))
- Propellor.PropAccum: (&) :: (PropAccum h, IsProp p) => h -> p -> h
+ Propellor.PropAccum: (&) :: (IsProp p, MetaTypes y ~ GetMetaTypes p, CheckCombinable x y ~ CanCombine) => Props (MetaTypes x) -> p -> Props (MetaTypes (Combine x y))
- Propellor.PropAccum: (&^) :: (PropAccum h, IsProp p) => h -> p -> h
+ Propellor.PropAccum: (&^) :: (IsProp p, MetaTypes y ~ GetMetaTypes p, CheckCombinable x y ~ CanCombine) => Props (MetaTypes x) -> p -> Props (MetaTypes (Combine x y))
- Propellor.PropAccum: host :: HostName -> Host
+ Propellor.PropAccum: host :: HostName -> Props metatypes -> Host
- Propellor.Property: doNothing :: Property NoInfo
+ Propellor.Property: doNothing :: SingI t => Property (MetaTypes t)
- Propellor.Property: ensureProperty :: Property NoInfo -> Propellor Result
+ Propellor.Property: ensureProperty :: (Cannot_ensureProperty_WithInfo inner ~ True, (Targets inner `NotSuperset` Targets outer) ~ CanCombine) => OuterMetaTypesWitness outer -> Property (MetaTypes inner) -> Propellor Result
- Propellor.Property: property :: Desc -> Propellor Result -> Property NoInfo
+ Propellor.Property: property :: SingI metatypes => Desc -> Propellor Result -> Property (MetaTypes metatypes)
- Propellor.Property: revert :: RevertableProperty i -> RevertableProperty i
+ Propellor.Property: revert :: RevertableProperty setup undo -> RevertableProperty undo setup
- Propellor.Property: unsupportedOS :: Propellor a
+ Propellor.Property: unsupportedOS :: Property UnixLike
- Propellor.Property: withOS :: Desc -> (Maybe System -> Propellor Result) -> Property NoInfo
+ Propellor.Property: withOS :: (SingI metatypes) => Desc -> (OuterMetaTypesWitness '[] -> Maybe System -> Propellor Result) -> Property (MetaTypes metatypes)
- Propellor.Property.Aiccu: hasConfig :: TunnelId -> UserName -> Property HasInfo
+ Propellor.Property.Aiccu: hasConfig :: TunnelId -> UserName -> Property (HasInfo + DebianLike)
- Propellor.Property.Aiccu: installed :: Property NoInfo
+ Propellor.Property.Aiccu: installed :: Property DebianLike
- Propellor.Property.Aiccu: restarted :: Property NoInfo
+ Propellor.Property.Aiccu: restarted :: Property DebianLike
- Propellor.Property.Apache: httpsVirtualHost :: Domain -> WebRoot -> AgreeTOS -> RevertableProperty NoInfo
+ Propellor.Property.Apache: httpsVirtualHost :: Domain -> WebRoot -> AgreeTOS -> RevertableProperty DebianLike DebianLike
- Propellor.Property.Apache: httpsVirtualHost' :: Domain -> WebRoot -> AgreeTOS -> [ConfigLine] -> RevertableProperty NoInfo
+ Propellor.Property.Apache: httpsVirtualHost' :: Domain -> WebRoot -> AgreeTOS -> [ConfigLine] -> RevertableProperty DebianLike DebianLike
- Propellor.Property.Apache: installed :: Property NoInfo
+ Propellor.Property.Apache: installed :: Property DebianLike
- Propellor.Property.Apache: listenPorts :: [Port] -> Property NoInfo
+ Propellor.Property.Apache: listenPorts :: [Port] -> Property DebianLike
- Propellor.Property.Apache: modEnabled :: String -> RevertableProperty NoInfo
+ Propellor.Property.Apache: modEnabled :: String -> RevertableProperty DebianLike DebianLike
- Propellor.Property.Apache: multiSSL :: Property NoInfo
+ Propellor.Property.Apache: multiSSL :: Property DebianLike
- Propellor.Property.Apache: reloaded :: Property NoInfo
+ Propellor.Property.Apache: reloaded :: Property DebianLike
- Propellor.Property.Apache: restarted :: Property NoInfo
+ Propellor.Property.Apache: restarted :: Property DebianLike
- Propellor.Property.Apache: siteAvailable :: Domain -> ConfigFile -> Property NoInfo
+ Propellor.Property.Apache: siteAvailable :: Domain -> ConfigFile -> Property DebianLike
- Propellor.Property.Apache: siteDisabled :: Domain -> Property NoInfo
+ Propellor.Property.Apache: siteDisabled :: Domain -> Property DebianLike
- Propellor.Property.Apache: siteEnabled :: Domain -> ConfigFile -> RevertableProperty NoInfo
+ Propellor.Property.Apache: siteEnabled :: Domain -> ConfigFile -> RevertableProperty DebianLike DebianLike
- Propellor.Property.Apache: siteEnabled' :: Domain -> ConfigFile -> Property NoInfo
+ Propellor.Property.Apache: siteEnabled' :: Domain -> ConfigFile -> Property DebianLike
- Propellor.Property.Apache: virtualHost :: Domain -> Port -> WebRoot -> RevertableProperty NoInfo
+ Propellor.Property.Apache: virtualHost :: Domain -> Port -> WebRoot -> RevertableProperty DebianLike DebianLike
- Propellor.Property.Apache: virtualHost' :: Domain -> Port -> WebRoot -> [ConfigLine] -> RevertableProperty NoInfo
+ Propellor.Property.Apache: virtualHost' :: Domain -> Port -> WebRoot -> [ConfigLine] -> RevertableProperty DebianLike DebianLike
- Propellor.Property.Apt: autoRemove :: Property NoInfo
+ Propellor.Property.Apt: autoRemove :: Property DebianLike
- Propellor.Property.Apt: buildDep :: [Package] -> Property NoInfo
+ Propellor.Property.Apt: buildDep :: [Package] -> Property DebianLike
- Propellor.Property.Apt: buildDepIn :: FilePath -> Property NoInfo
+ Propellor.Property.Apt: buildDepIn :: FilePath -> Property DebianLike
- Propellor.Property.Apt: cacheCleaned :: Property NoInfo
+ Propellor.Property.Apt: cacheCleaned :: Property DebianLike
- Propellor.Property.Apt: hasForeignArch :: String -> Property NoInfo
+ Propellor.Property.Apt: hasForeignArch :: String -> Property DebianLike
- Propellor.Property.Apt: installed :: [Package] -> Property NoInfo
+ Propellor.Property.Apt: installed :: [Package] -> Property DebianLike
- Propellor.Property.Apt: installed' :: [String] -> [Package] -> Property NoInfo
+ Propellor.Property.Apt: installed' :: [String] -> [Package] -> Property DebianLike
- Propellor.Property.Apt: installedBackport :: [Package] -> Property NoInfo
+ Propellor.Property.Apt: installedBackport :: [Package] -> Property Debian
- Propellor.Property.Apt: installedMin :: [Package] -> Property NoInfo
+ Propellor.Property.Apt: installedMin :: [Package] -> Property DebianLike
- Propellor.Property.Apt: pendingConfigured :: Property NoInfo
+ Propellor.Property.Apt: pendingConfigured :: Property DebianLike
- Propellor.Property.Apt: reConfigure :: Package -> [(DebconfTemplate, DebconfTemplateType, DebconfTemplateValue)] -> Property NoInfo
+ Propellor.Property.Apt: reConfigure :: Package -> [(DebconfTemplate, DebconfTemplateType, DebconfTemplateValue)] -> Property DebianLike
- Propellor.Property.Apt: removed :: [Package] -> Property NoInfo
+ Propellor.Property.Apt: removed :: [Package] -> Property DebianLike
- Propellor.Property.Apt: robustly :: (Combines (Property i) (Property NoInfo)) => Property i -> Property i
+ Propellor.Property.Apt: robustly :: Property DebianLike -> Property DebianLike
- Propellor.Property.Apt: runApt :: [String] -> UncheckedProperty NoInfo
+ Propellor.Property.Apt: runApt :: [String] -> UncheckedProperty DebianLike
- Propellor.Property.Apt: safeUpgrade :: Property NoInfo
+ Propellor.Property.Apt: safeUpgrade :: Property DebianLike
- Propellor.Property.Apt: serviceInstalledRunning :: Package -> Property NoInfo
+ Propellor.Property.Apt: serviceInstalledRunning :: Package -> Property DebianLike
- Propellor.Property.Apt: setSourcesList :: [Line] -> Property NoInfo
+ Propellor.Property.Apt: setSourcesList :: [Line] -> Property DebianLike
- Propellor.Property.Apt: setSourcesListD :: [Line] -> FilePath -> Property NoInfo
+ Propellor.Property.Apt: setSourcesListD :: [Line] -> FilePath -> Property DebianLike
- Propellor.Property.Apt: stdSourcesList :: Property NoInfo
+ Propellor.Property.Apt: stdSourcesList :: Property Debian
- Propellor.Property.Apt: stdSourcesList' :: DebianSuite -> [SourcesGenerator] -> Property NoInfo
+ Propellor.Property.Apt: stdSourcesList' :: DebianSuite -> [SourcesGenerator] -> Property Debian
- Propellor.Property.Apt: stdSourcesListFor :: DebianSuite -> Property NoInfo
+ Propellor.Property.Apt: stdSourcesListFor :: DebianSuite -> Property Debian
- Propellor.Property.Apt: trustsKey :: AptKey -> RevertableProperty NoInfo
+ Propellor.Property.Apt: trustsKey :: AptKey -> RevertableProperty DebianLike DebianLike
- Propellor.Property.Apt: trustsKey' :: AptKey -> Property NoInfo
+ Propellor.Property.Apt: trustsKey' :: AptKey -> Property DebianLike
- Propellor.Property.Apt: unattendedUpgrades :: RevertableProperty NoInfo
+ Propellor.Property.Apt: unattendedUpgrades :: RevertableProperty DebianLike DebianLike
- Propellor.Property.Apt: untrustKey :: AptKey -> Property NoInfo
+ Propellor.Property.Apt: untrustKey :: AptKey -> Property DebianLike
- Propellor.Property.Apt: update :: Property NoInfo
+ Propellor.Property.Apt: update :: Property DebianLike
- Propellor.Property.Apt: upgrade :: Property NoInfo
+ Propellor.Property.Apt: upgrade :: Property DebianLike
- Propellor.Property.Apt: upgrade' :: String -> Property NoInfo
+ Propellor.Property.Apt: upgrade' :: String -> Property DebianLike
- Propellor.Property.Chroot: bootstrapped :: ChrootBootstrapper b => b -> FilePath -> Chroot
+ Propellor.Property.Chroot: bootstrapped :: ChrootBootstrapper b => b -> FilePath -> Props metatypes -> Chroot
- Propellor.Property.Chroot: buildchroot :: ChrootBootstrapper b => b -> Maybe System -> FilePath -> Either String (Property HasInfo)
+ Propellor.Property.Chroot: buildchroot :: ChrootBootstrapper b => b -> Maybe System -> FilePath -> Either String (Property Linux)
- Propellor.Property.Chroot: debootstrapped :: DebootstrapConfig -> FilePath -> Chroot
+ Propellor.Property.Chroot: debootstrapped :: DebootstrapConfig -> FilePath -> Props metatypes -> Chroot
- Propellor.Property.Chroot: noServices :: RevertableProperty NoInfo
+ Propellor.Property.Chroot: noServices :: RevertableProperty UnixLike UnixLike
- Propellor.Property.Chroot: propagateChrootInfo :: (IsProp (Property i)) => Chroot -> Property i -> Property HasInfo
+ Propellor.Property.Chroot: propagateChrootInfo :: Chroot -> Property Linux -> Property (HasInfo + Linux)
- Propellor.Property.Chroot: propellChroot :: Chroot -> ([String] -> IO (CreateProcess, IO ())) -> Bool -> Property NoInfo
+ Propellor.Property.Chroot: propellChroot :: Chroot -> ([String] -> IO (CreateProcess, IO ())) -> Bool -> Property UnixLike
- Propellor.Property.Chroot: provisioned :: Chroot -> RevertableProperty HasInfo
+ Propellor.Property.Chroot: provisioned :: Chroot -> RevertableProperty (HasInfo + Linux) Linux
- Propellor.Property.Chroot: provisioned' :: (Property HasInfo -> Property HasInfo) -> Chroot -> Bool -> RevertableProperty HasInfo
+ Propellor.Property.Chroot: provisioned' :: (Property Linux -> Property (HasInfo + Linux)) -> Chroot -> Bool -> RevertableProperty (HasInfo + Linux) Linux
- Propellor.Property.Cmd: cmdProperty :: String -> [String] -> UncheckedProperty NoInfo
+ Propellor.Property.Cmd: cmdProperty :: String -> [String] -> UncheckedProperty UnixLike
- Propellor.Property.Cmd: cmdProperty' :: String -> [String] -> (CreateProcess -> CreateProcess) -> UncheckedProperty NoInfo
+ Propellor.Property.Cmd: cmdProperty' :: String -> [String] -> (CreateProcess -> CreateProcess) -> UncheckedProperty UnixLike
- Propellor.Property.Cmd: cmdPropertyEnv :: String -> [String] -> [(String, String)] -> UncheckedProperty NoInfo
+ Propellor.Property.Cmd: cmdPropertyEnv :: String -> [String] -> [(String, String)] -> UncheckedProperty UnixLike
- Propellor.Property.Cmd: scriptProperty :: Script -> UncheckedProperty NoInfo
+ Propellor.Property.Cmd: scriptProperty :: Script -> UncheckedProperty UnixLike
- Propellor.Property.Cmd: userScriptProperty :: User -> Script -> UncheckedProperty NoInfo
+ Propellor.Property.Cmd: userScriptProperty :: User -> Script -> UncheckedProperty UnixLike
- Propellor.Property.Concurrent: concurrentList :: IO Int -> Desc -> PropList -> Property HasInfo
+ Propellor.Property.Concurrent: concurrentList :: SingI metatypes => IO Int -> Desc -> Props (MetaTypes metatypes) -> Property (MetaTypes metatypes)
- Propellor.Property.Concurrent: props :: PropList
+ Propellor.Property.Concurrent: props :: Props UnixLike
- Propellor.Property.Conductor: conducts :: Conductable c => c -> RevertableProperty HasInfo
+ Propellor.Property.Conductor: conducts :: Conductable c => c -> RevertableProperty (HasInfo + UnixLike) (HasInfo + UnixLike)
- Propellor.Property.ConfFile: adjustSection :: Desc -> SectionStart -> SectionPast -> AdjustSection -> InsertSection -> FilePath -> Property NoInfo
+ Propellor.Property.ConfFile: adjustSection :: Desc -> SectionStart -> SectionPast -> AdjustSection -> InsertSection -> FilePath -> Property UnixLike
- Propellor.Property.ConfFile: containsIniSetting :: FilePath -> (IniSection, IniKey, String) -> Property NoInfo
+ Propellor.Property.ConfFile: containsIniSetting :: FilePath -> (IniSection, IniKey, String) -> Property UnixLike
- Propellor.Property.ConfFile: lacksIniSection :: FilePath -> IniSection -> Property NoInfo
+ Propellor.Property.ConfFile: lacksIniSection :: FilePath -> IniSection -> Property UnixLike
- Propellor.Property.Cron: job :: Desc -> Times -> User -> FilePath -> String -> Property NoInfo
+ Propellor.Property.Cron: job :: Desc -> Times -> User -> FilePath -> String -> Property DebianLike
- Propellor.Property.Cron: niceJob :: Desc -> Times -> User -> FilePath -> String -> Property NoInfo
+ Propellor.Property.Cron: niceJob :: Desc -> Times -> User -> FilePath -> String -> Property DebianLike
- Propellor.Property.Cron: runPropellor :: Times -> Property NoInfo
+ Propellor.Property.Cron: runPropellor :: Times -> Property UnixLike
- Propellor.Property.DebianMirror: mirror :: DebianMirror -> Property NoInfo
+ Propellor.Property.DebianMirror: mirror :: DebianMirror -> Property DebianLike
- Propellor.Property.Debootstrap: built :: FilePath -> System -> DebootstrapConfig -> Property HasInfo
+ Propellor.Property.Debootstrap: built :: FilePath -> System -> DebootstrapConfig -> Property Linux
- Propellor.Property.Debootstrap: built' :: (Combines (Property NoInfo) (Property i)) => Property i -> FilePath -> System -> DebootstrapConfig -> Property (CInfo NoInfo i)
+ Propellor.Property.Debootstrap: built' :: Property Linux -> FilePath -> System -> DebootstrapConfig -> Property Linux
- Propellor.Property.Debootstrap: installed :: RevertableProperty NoInfo
+ Propellor.Property.Debootstrap: installed :: RevertableProperty Linux Linux
- Propellor.Property.Debootstrap: sourceInstall :: Property NoInfo
+ Propellor.Property.Debootstrap: sourceInstall :: Property Linux
- Propellor.Property.DiskImage: imageBuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty HasInfo
+ Propellor.Property.DiskImage: imageBuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty (HasInfo + Linux) Linux
- Propellor.Property.DiskImage: imageBuiltFrom :: DiskImage -> FilePath -> TableType -> Finalization -> [PartSpec] -> RevertableProperty NoInfo
+ Propellor.Property.DiskImage: imageBuiltFrom :: DiskImage -> FilePath -> TableType -> Finalization -> [PartSpec] -> RevertableProperty (HasInfo + Linux) UnixLike
- Propellor.Property.DiskImage: imageExists :: FilePath -> ByteSize -> Property NoInfo
+ Propellor.Property.DiskImage: imageExists :: FilePath -> ByteSize -> Property Linux
- Propellor.Property.DiskImage: imageRebuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty HasInfo
+ Propellor.Property.DiskImage: imageRebuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty (HasInfo + Linux) Linux
- Propellor.Property.DiskImage: type Finalization = (Property NoInfo, FilePath -> [LoopDev] -> Property NoInfo)
+ Propellor.Property.DiskImage: type Finalization = (Property Linux, FilePath -> [LoopDev] -> Property Linux)
- Propellor.Property.Dns: primary :: [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty HasInfo
+ Propellor.Property.Dns: primary :: [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty (HasInfo + DebianLike) DebianLike
- Propellor.Property.Dns: secondary :: [Host] -> Domain -> RevertableProperty HasInfo
+ Propellor.Property.Dns: secondary :: [Host] -> Domain -> RevertableProperty (HasInfo + DebianLike) DebianLike
- Propellor.Property.Dns: secondaryFor :: [HostName] -> [Host] -> Domain -> RevertableProperty HasInfo
+ Propellor.Property.Dns: secondaryFor :: [HostName] -> [Host] -> Domain -> RevertableProperty (HasInfo + DebianLike) DebianLike
- Propellor.Property.Dns: signedPrimary :: Recurrance -> [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty HasInfo
+ Propellor.Property.Dns: signedPrimary :: Recurrance -> [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty (HasInfo + DebianLike) DebianLike
- Propellor.Property.DnsSec: forceZoneSigned :: Domain -> FilePath -> Property NoInfo
+ Propellor.Property.DnsSec: forceZoneSigned :: Domain -> FilePath -> Property UnixLike
- Propellor.Property.DnsSec: keysInstalled :: Domain -> RevertableProperty HasInfo
+ Propellor.Property.DnsSec: keysInstalled :: Domain -> RevertableProperty (HasInfo + UnixLike) UnixLike
- Propellor.Property.DnsSec: zoneSigned :: Domain -> FilePath -> RevertableProperty HasInfo
+ Propellor.Property.DnsSec: zoneSigned :: Domain -> FilePath -> RevertableProperty (HasInfo + UnixLike) UnixLike
- Propellor.Property.Docker: configured :: Property HasInfo
+ Propellor.Property.Docker: configured :: Property (HasInfo + DebianLike)
- Propellor.Property.Docker: container :: ContainerName -> Image -> Container
+ Propellor.Property.Docker: container :: ContainerName -> Image -> Props metatypes -> Container
- Propellor.Property.Docker: cpuShares :: Int -> Property HasInfo
+ Propellor.Property.Docker: cpuShares :: Int -> Property (HasInfo + Linux)
- Propellor.Property.Docker: dns :: String -> Property HasInfo
+ Propellor.Property.Docker: dns :: String -> Property (HasInfo + Linux)
- Propellor.Property.Docker: docked :: Container -> RevertableProperty HasInfo
+ Propellor.Property.Docker: docked :: Container -> RevertableProperty (HasInfo + Linux) (HasInfo + Linux)
- Propellor.Property.Docker: environment :: (String, String) -> Property HasInfo
+ Propellor.Property.Docker: environment :: (String, String) -> Property (HasInfo + Linux)
- Propellor.Property.Docker: expose :: String -> Property HasInfo
+ Propellor.Property.Docker: expose :: String -> Property (HasInfo + Linux)
- Propellor.Property.Docker: garbageCollected :: Property NoInfo
+ Propellor.Property.Docker: garbageCollected :: Property Linux
- Propellor.Property.Docker: hostname :: String -> Property HasInfo
+ Propellor.Property.Docker: hostname :: String -> Property (HasInfo + Linux)
- Propellor.Property.Docker: imageBuilt :: HasImage c => FilePath -> c -> Property NoInfo
+ Propellor.Property.Docker: imageBuilt :: HasImage c => FilePath -> c -> Property Linux
- Propellor.Property.Docker: imagePulled :: HasImage c => c -> Property NoInfo
+ Propellor.Property.Docker: imagePulled :: HasImage c => c -> Property Linux
- Propellor.Property.Docker: installed :: Property NoInfo
+ Propellor.Property.Docker: installed :: Property DebianLike
- Propellor.Property.Docker: link :: ContainerName -> ContainerAlias -> Property HasInfo
+ Propellor.Property.Docker: link :: ContainerName -> ContainerAlias -> Property (HasInfo + Linux)
- Propellor.Property.Docker: memory :: String -> Property HasInfo
+ Propellor.Property.Docker: memory :: String -> Property (HasInfo + Linux)
- Propellor.Property.Docker: memoryLimited :: Property NoInfo
+ Propellor.Property.Docker: memoryLimited :: Property DebianLike
- Propellor.Property.Docker: publish :: Publishable p => p -> Property HasInfo
+ Propellor.Property.Docker: publish :: Publishable p => p -> Property (HasInfo + Linux)
- Propellor.Property.Docker: restartAlways :: Property HasInfo
+ Propellor.Property.Docker: restartAlways :: Property (HasInfo + Linux)
- Propellor.Property.Docker: restartNever :: Property HasInfo
+ Propellor.Property.Docker: restartNever :: Property (HasInfo + Linux)
- Propellor.Property.Docker: restartOnFailure :: Maybe Int -> Property HasInfo
+ Propellor.Property.Docker: restartOnFailure :: Maybe Int -> Property (HasInfo + Linux)
- Propellor.Property.Docker: tweaked :: Property NoInfo
+ Propellor.Property.Docker: tweaked :: Property Linux
- Propellor.Property.Docker: user :: String -> Property HasInfo
+ Propellor.Property.Docker: user :: String -> Property (HasInfo + Linux)
- Propellor.Property.Docker: volume :: Mountable v => v -> Property HasInfo
+ Propellor.Property.Docker: volume :: Mountable v => v -> Property (HasInfo + Linux)
- Propellor.Property.Docker: volumes_from :: ContainerName -> Property HasInfo
+ Propellor.Property.Docker: volumes_from :: ContainerName -> Property (HasInfo + Linux)
- Propellor.Property.Docker: workdir :: String -> Property HasInfo
+ Propellor.Property.Docker: workdir :: String -> Property (HasInfo + Linux)
- Propellor.Property.Fail2Ban: installed :: Property NoInfo
+ Propellor.Property.Fail2Ban: installed :: Property DebianLike
- Propellor.Property.Fail2Ban: jailConfigured :: Jail -> IniKey -> String -> Property NoInfo
+ Propellor.Property.Fail2Ban: jailConfigured :: Jail -> IniKey -> String -> Property UnixLike
- Propellor.Property.Fail2Ban: jailEnabled :: Jail -> Property NoInfo
+ Propellor.Property.Fail2Ban: jailEnabled :: Jail -> Property DebianLike
- Propellor.Property.Fail2Ban: reloaded :: Property NoInfo
+ Propellor.Property.Fail2Ban: reloaded :: Property DebianLike
- Propellor.Property.File: basedOn :: FilePath -> (FilePath, [Line] -> [Line]) -> Property NoInfo
+ Propellor.Property.File: basedOn :: FilePath -> (FilePath, [Line] -> [Line]) -> Property UnixLike
- Propellor.Property.File: containsLine :: FilePath -> Line -> Property NoInfo
+ Propellor.Property.File: containsLine :: FilePath -> Line -> Property UnixLike
- Propellor.Property.File: containsLines :: FilePath -> [Line] -> Property NoInfo
+ Propellor.Property.File: containsLines :: FilePath -> [Line] -> Property UnixLike
- Propellor.Property.File: dirExists :: FilePath -> Property NoInfo
+ Propellor.Property.File: dirExists :: FilePath -> Property UnixLike
- Propellor.Property.File: fileProperty :: Desc -> ([Line] -> [Line]) -> FilePath -> Property NoInfo
+ Propellor.Property.File: fileProperty :: Desc -> ([Line] -> [Line]) -> FilePath -> Property UnixLike
- Propellor.Property.File: fileProperty' :: (FilePath -> String -> IO ()) -> Desc -> ([Line] -> [Line]) -> FilePath -> Property NoInfo
+ Propellor.Property.File: fileProperty' :: (FilePath -> String -> IO ()) -> Desc -> ([Line] -> [Line]) -> FilePath -> Property UnixLike
- Propellor.Property.File: hasContent :: FilePath -> [Line] -> Property NoInfo
+ Propellor.Property.File: hasContent :: FilePath -> [Line] -> Property UnixLike
- Propellor.Property.File: hasContentProtected :: FilePath -> [Line] -> Property NoInfo
+ Propellor.Property.File: hasContentProtected :: FilePath -> [Line] -> Property UnixLike
- Propellor.Property.File: hasPrivContent :: IsContext c => FilePath -> c -> Property HasInfo
+ Propellor.Property.File: hasPrivContent :: IsContext c => FilePath -> c -> Property (HasInfo + UnixLike)
- Propellor.Property.File: hasPrivContent' :: (IsContext c, IsPrivDataSource s) => (FilePath -> String -> IO ()) -> s -> FilePath -> c -> Property HasInfo
+ Propellor.Property.File: hasPrivContent' :: (IsContext c, IsPrivDataSource s) => (FilePath -> String -> IO ()) -> s -> FilePath -> c -> Property (HasInfo + UnixLike)
- Propellor.Property.File: hasPrivContentExposed :: IsContext c => FilePath -> c -> Property HasInfo
+ Propellor.Property.File: hasPrivContentExposed :: IsContext c => FilePath -> c -> Property (HasInfo + UnixLike)
- Propellor.Property.File: hasPrivContentExposedFrom :: (IsContext c, IsPrivDataSource s) => s -> FilePath -> c -> Property HasInfo
+ Propellor.Property.File: hasPrivContentExposedFrom :: (IsContext c, IsPrivDataSource s) => s -> FilePath -> c -> Property (HasInfo + UnixLike)
- Propellor.Property.File: hasPrivContentFrom :: (IsContext c, IsPrivDataSource s) => s -> FilePath -> c -> Property HasInfo
+ Propellor.Property.File: hasPrivContentFrom :: (IsContext c, IsPrivDataSource s) => s -> FilePath -> c -> Property (HasInfo + UnixLike)
- Propellor.Property.File: isCopyOf :: FilePath -> FilePath -> Property NoInfo
+ Propellor.Property.File: isCopyOf :: FilePath -> FilePath -> Property UnixLike
- Propellor.Property.File: isSymlinkedTo :: FilePath -> LinkTarget -> Property NoInfo
+ Propellor.Property.File: isSymlinkedTo :: FilePath -> LinkTarget -> Property UnixLike
- Propellor.Property.File: lacksLine :: FilePath -> Line -> Property NoInfo
+ Propellor.Property.File: lacksLine :: FilePath -> Line -> Property UnixLike
- Propellor.Property.File: lacksLines :: FilePath -> [Line] -> Property NoInfo
+ Propellor.Property.File: lacksLines :: FilePath -> [Line] -> Property UnixLike
- Propellor.Property.File: mode :: FilePath -> FileMode -> Property NoInfo
+ Propellor.Property.File: mode :: FilePath -> FileMode -> Property UnixLike
- Propellor.Property.File: notPresent :: FilePath -> Property NoInfo
+ Propellor.Property.File: notPresent :: FilePath -> Property UnixLike
- Propellor.Property.File: ownerGroup :: FilePath -> User -> Group -> Property NoInfo
+ Propellor.Property.File: ownerGroup :: FilePath -> User -> Group -> Property UnixLike
- Propellor.Property.Firewall: installed :: Property NoInfo
+ Propellor.Property.Firewall: installed :: Property DebianLike
- Propellor.Property.Firewall: rule :: Chain -> Table -> Target -> Rules -> Property NoInfo
+ Propellor.Property.Firewall: rule :: Chain -> Table -> Target -> Rules -> Property Linux
- Propellor.Property.FreeBSD.Pkg: installed :: Package -> Property NoInfo
+ Propellor.Property.FreeBSD.Pkg: installed :: Package -> Property FreeBSD
- Propellor.Property.FreeBSD.Pkg: pkgCmdProperty :: String -> [String] -> UncheckedProperty NoInfo
+ Propellor.Property.FreeBSD.Pkg: pkgCmdProperty :: String -> [String] -> UncheckedProperty FreeBSD
- Propellor.Property.FreeBSD.Pkg: update :: Property HasInfo
+ Propellor.Property.FreeBSD.Pkg: update :: Property (HasInfo + FreeBSD)
- Propellor.Property.FreeBSD.Pkg: upgrade :: Property HasInfo
+ Propellor.Property.FreeBSD.Pkg: upgrade :: Property (HasInfo + FreeBSD)
- Propellor.Property.FreeBSD.Poudriere: jail :: Jail -> Property NoInfo
+ Propellor.Property.FreeBSD.Poudriere: jail :: Jail -> Property FreeBSD
- Propellor.Property.FreeBSD.Poudriere: poudriere :: Poudriere -> Property HasInfo
+ Propellor.Property.FreeBSD.Poudriere: poudriere :: Poudriere -> Property (HasInfo + FreeBSD)
- Propellor.Property.FreeBSD.Poudriere: setConfigured :: Property HasInfo
+ Propellor.Property.FreeBSD.Poudriere: setConfigured :: Property (HasInfo + FreeBSD)
- Propellor.Property.Git: bareRepo :: FilePath -> User -> GitShared -> Property NoInfo
+ Propellor.Property.Git: bareRepo :: FilePath -> User -> GitShared -> Property UnixLike
- Propellor.Property.Git: bareRepoDefaultBranch :: FilePath -> String -> Property NoInfo
+ Propellor.Property.Git: bareRepoDefaultBranch :: FilePath -> String -> Property UnixLike
- Propellor.Property.Git: cloned :: User -> RepoUrl -> FilePath -> Maybe Branch -> Property NoInfo
+ Propellor.Property.Git: cloned :: User -> RepoUrl -> FilePath -> Maybe Branch -> Property DebianLike
- Propellor.Property.Git: daemonRunning :: FilePath -> RevertableProperty NoInfo
+ Propellor.Property.Git: daemonRunning :: FilePath -> RevertableProperty DebianLike DebianLike
- Propellor.Property.Git: installed :: Property NoInfo
+ Propellor.Property.Git: installed :: Property DebianLike
- Propellor.Property.Git: repoAcceptsNonFFs :: FilePath -> RevertableProperty NoInfo
+ Propellor.Property.Git: repoAcceptsNonFFs :: FilePath -> RevertableProperty UnixLike UnixLike
- Propellor.Property.Git: repoConfigured :: FilePath -> (String, String) -> Property NoInfo
+ Propellor.Property.Git: repoConfigured :: FilePath -> (String, String) -> Property UnixLike
- Propellor.Property.Gpg: installed :: Property NoInfo
+ Propellor.Property.Gpg: installed :: Property DebianLike
- Propellor.Property.Gpg: keyImported :: GpgKeyId -> User -> Property HasInfo
+ Propellor.Property.Gpg: keyImported :: GpgKeyId -> User -> Property (HasInfo + DebianLike)
- Propellor.Property.Group: exists :: Group -> Maybe GID -> Property NoInfo
+ Propellor.Property.Group: exists :: Group -> Maybe GID -> Property UnixLike
- Propellor.Property.Grub: boots :: OSDevice -> Property NoInfo
+ Propellor.Property.Grub: boots :: OSDevice -> Property Linux
- Propellor.Property.Grub: chainPVGrub :: GrubDevice -> GrubDevice -> TimeoutSecs -> Property NoInfo
+ Propellor.Property.Grub: chainPVGrub :: GrubDevice -> GrubDevice -> TimeoutSecs -> Property DebianLike
- Propellor.Property.Grub: installed :: BIOS -> Property NoInfo
+ Propellor.Property.Grub: installed :: BIOS -> Property DebianLike
- Propellor.Property.Grub: installed' :: BIOS -> Property NoInfo
+ Propellor.Property.Grub: installed' :: BIOS -> Property Linux
- Propellor.Property.Grub: mkConfig :: Property NoInfo
+ Propellor.Property.Grub: mkConfig :: Property DebianLike
- Propellor.Property.HostingProvider.CloudAtCost: decruft :: Property NoInfo
+ Propellor.Property.HostingProvider.CloudAtCost: decruft :: Property DebianLike
- Propellor.Property.HostingProvider.DigitalOcean: distroKernel :: Property NoInfo
+ Propellor.Property.HostingProvider.DigitalOcean: distroKernel :: Property DebianLike
- Propellor.Property.HostingProvider.Linode: chainPVGrub :: TimeoutSecs -> Property NoInfo
+ Propellor.Property.HostingProvider.Linode: chainPVGrub :: TimeoutSecs -> Property DebianLike
- Propellor.Property.HostingProvider.Linode: mlocateEnabled :: Property NoInfo
+ Propellor.Property.HostingProvider.Linode: mlocateEnabled :: Property DebianLike
- Propellor.Property.Hostname: sane :: Property NoInfo
+ Propellor.Property.Hostname: sane :: Property UnixLike
- Propellor.Property.Hostname: sane' :: ExtractDomain -> Property NoInfo
+ Propellor.Property.Hostname: sane' :: ExtractDomain -> Property UnixLike
- Propellor.Property.Hostname: searchDomain :: Property NoInfo
+ Propellor.Property.Hostname: searchDomain :: Property UnixLike
- Propellor.Property.Hostname: searchDomain' :: ExtractDomain -> Property NoInfo
+ Propellor.Property.Hostname: searchDomain' :: ExtractDomain -> Property UnixLike
- Propellor.Property.Hostname: setTo :: HostName -> Property NoInfo
+ Propellor.Property.Hostname: setTo :: HostName -> Property UnixLike
- Propellor.Property.Hostname: setTo' :: ExtractDomain -> HostName -> Property NoInfo
+ Propellor.Property.Hostname: setTo' :: ExtractDomain -> HostName -> Property UnixLike
- Propellor.Property.Journald: configured :: Option -> String -> Property NoInfo
+ Propellor.Property.Journald: configured :: Option -> String -> Property Linux
- Propellor.Property.Journald: configuredSize :: Option -> DataSize -> Property NoInfo
+ Propellor.Property.Journald: configuredSize :: Option -> DataSize -> Property Linux
- Propellor.Property.Journald: runtimeKeepFree :: DataSize -> Property NoInfo
+ Propellor.Property.Journald: runtimeKeepFree :: DataSize -> Property Linux
- Propellor.Property.Journald: runtimeMaxFileSize :: DataSize -> Property NoInfo
+ Propellor.Property.Journald: runtimeMaxFileSize :: DataSize -> Property Linux
- Propellor.Property.Journald: runtimeMaxUse :: DataSize -> Property NoInfo
+ Propellor.Property.Journald: runtimeMaxUse :: DataSize -> Property Linux
- Propellor.Property.Journald: systemKeepFree :: DataSize -> Property NoInfo
+ Propellor.Property.Journald: systemKeepFree :: DataSize -> Property Linux
- Propellor.Property.Journald: systemMaxFileSize :: DataSize -> Property NoInfo
+ Propellor.Property.Journald: systemMaxFileSize :: DataSize -> Property Linux
- Propellor.Property.Journald: systemMaxUse :: DataSize -> Property NoInfo
+ Propellor.Property.Journald: systemMaxUse :: DataSize -> Property Linux
- Propellor.Property.Kerberos: adminServerInstalled :: Property NoInfo
+ Propellor.Property.Kerberos: adminServerInstalled :: Property DebianLike
- Propellor.Property.Kerberos: installed :: Property NoInfo
+ Propellor.Property.Kerberos: installed :: Property DebianLike
- Propellor.Property.Kerberos: k5login :: User -> [Principal] -> Property NoInfo
+ Propellor.Property.Kerberos: k5login :: User -> [Principal] -> Property UnixLike
- Propellor.Property.Kerberos: kdcInstalled :: Property NoInfo
+ Propellor.Property.Kerberos: kdcInstalled :: Property DebianLike
- Propellor.Property.Kerberos: kpropAcls :: [String] -> Property NoInfo
+ Propellor.Property.Kerberos: kpropAcls :: [String] -> Property UnixLike
- Propellor.Property.Kerberos: kpropServerInstalled :: Property HasInfo
+ Propellor.Property.Kerberos: kpropServerInstalled :: Property DebianLike
- Propellor.Property.LetsEncrypt: installed :: Property NoInfo
+ Propellor.Property.LetsEncrypt: installed :: Property DebianLike
- Propellor.Property.LetsEncrypt: letsEncrypt :: AgreeTOS -> Domain -> WebRoot -> Property NoInfo
+ Propellor.Property.LetsEncrypt: letsEncrypt :: AgreeTOS -> Domain -> WebRoot -> Property DebianLike
- Propellor.Property.LetsEncrypt: letsEncrypt' :: AgreeTOS -> Domain -> [Domain] -> WebRoot -> Property NoInfo
+ Propellor.Property.LetsEncrypt: letsEncrypt' :: AgreeTOS -> Domain -> [Domain] -> WebRoot -> Property DebianLike
- Propellor.Property.LightDM: autoLogin :: User -> Property NoInfo
+ Propellor.Property.LightDM: autoLogin :: User -> Property UnixLike
- Propellor.Property.LightDM: installed :: Property NoInfo
+ Propellor.Property.LightDM: installed :: Property DebianLike
- Propellor.Property.List: combineProperties :: PropertyList l => Desc -> l -> Property (PropertyListType l)
+ Propellor.Property.List: combineProperties :: SingI metatypes => Desc -> Props (MetaTypes metatypes) -> Property (MetaTypes metatypes)
- Propellor.Property.List: propertyList :: PropertyList l => Desc -> l -> Property (PropertyListType l)
+ Propellor.Property.List: propertyList :: SingI metatypes => Desc -> Props (MetaTypes metatypes) -> Property (MetaTypes metatypes)
- Propellor.Property.List: props :: PropList
+ Propellor.Property.List: props :: Props UnixLike
- Propellor.Property.Locale: available :: Locale -> RevertableProperty NoInfo
+ Propellor.Property.Locale: available :: Locale -> RevertableProperty DebianLike DebianLike
- Propellor.Property.Locale: selectedFor :: Locale -> [LocaleVariable] -> RevertableProperty NoInfo
+ Propellor.Property.Locale: selectedFor :: Locale -> [LocaleVariable] -> RevertableProperty DebianLike DebianLike
- Propellor.Property.Logcheck: ignoreLines :: ReportLevel -> Service -> [String] -> Property NoInfo
+ Propellor.Property.Logcheck: ignoreLines :: ReportLevel -> Service -> [String] -> Property UnixLike
- Propellor.Property.Logcheck: installed :: Property NoInfo
+ Propellor.Property.Logcheck: installed :: Property DebianLike
- Propellor.Property.Mount: bindMount :: FilePath -> FilePath -> Property NoInfo
+ Propellor.Property.Mount: bindMount :: FilePath -> FilePath -> Property Linux
- Propellor.Property.Mount: fstabbed :: [MountPoint] -> [SwapPartition] -> Property NoInfo
+ Propellor.Property.Mount: fstabbed :: [MountPoint] -> [SwapPartition] -> Property Linux
- Propellor.Property.Mount: mounted :: FsType -> Source -> MountPoint -> MountOpts -> Property NoInfo
+ Propellor.Property.Mount: mounted :: FsType -> Source -> MountPoint -> MountOpts -> Property UnixLike
- Propellor.Property.Network: cleanInterfacesFile :: Property NoInfo
+ Propellor.Property.Network: cleanInterfacesFile :: Property DebianLike
- Propellor.Property.Network: dhcp :: Interface -> Property NoInfo
+ Propellor.Property.Network: dhcp :: Interface -> Property DebianLike
- Propellor.Property.Network: ifUp :: Interface -> Property NoInfo
+ Propellor.Property.Network: ifUp :: Interface -> Property DebianLike
- Propellor.Property.Network: interfacesDEnabled :: Property NoInfo
+ Propellor.Property.Network: interfacesDEnabled :: Property DebianLike
- Propellor.Property.Network: ipv6to4 :: Property NoInfo
+ Propellor.Property.Network: ipv6to4 :: Property DebianLike
- Propellor.Property.Network: static :: Interface -> Property NoInfo
+ Propellor.Property.Network: static :: Interface -> Property DebianLike
- Propellor.Property.Nginx: installed :: Property NoInfo
+ Propellor.Property.Nginx: installed :: Property DebianLike
- Propellor.Property.Nginx: reloaded :: Property NoInfo
+ Propellor.Property.Nginx: reloaded :: Property DebianLike
- Propellor.Property.Nginx: restarted :: Property NoInfo
+ Propellor.Property.Nginx: restarted :: Property DebianLike
- Propellor.Property.Nginx: siteAvailable :: HostName -> ConfigFile -> Property NoInfo
+ Propellor.Property.Nginx: siteAvailable :: HostName -> ConfigFile -> Property DebianLike
- Propellor.Property.Nginx: siteEnabled :: HostName -> ConfigFile -> RevertableProperty NoInfo
+ Propellor.Property.Nginx: siteEnabled :: HostName -> ConfigFile -> RevertableProperty DebianLike DebianLike
- Propellor.Property.OS: cleanInstallOnce :: Confirmation -> Property NoInfo
+ Propellor.Property.OS: cleanInstallOnce :: Confirmation -> Property Linux
- Propellor.Property.OS: oldOSRemoved :: Confirmation -> Property NoInfo
+ Propellor.Property.OS: oldOSRemoved :: Confirmation -> Property UnixLike
- Propellor.Property.OS: preserveNetwork :: Property NoInfo
+ Propellor.Property.OS: preserveNetwork :: Property DebianLike
- Propellor.Property.OS: preserveResolvConf :: Property NoInfo
+ Propellor.Property.OS: preserveResolvConf :: Property Linux
- Propellor.Property.OS: preserveRootSshAuthorized :: Property NoInfo
+ Propellor.Property.OS: preserveRootSshAuthorized :: Property UnixLike
- Propellor.Property.Obnam: backup :: FilePath -> Times -> [ObnamParam] -> NumClients -> Property NoInfo
+ Propellor.Property.Obnam: backup :: FilePath -> Times -> [ObnamParam] -> NumClients -> Property DebianLike
- Propellor.Property.Obnam: backup' :: FilePath -> Times -> [ObnamParam] -> NumClients -> Property NoInfo
+ Propellor.Property.Obnam: backup' :: FilePath -> Times -> [ObnamParam] -> NumClients -> Property DebianLike
- Propellor.Property.Obnam: backupEncrypted :: FilePath -> Times -> [ObnamParam] -> NumClients -> GpgKeyId -> Property HasInfo
+ Propellor.Property.Obnam: backupEncrypted :: FilePath -> Times -> [ObnamParam] -> NumClients -> GpgKeyId -> Property (HasInfo + DebianLike)
- Propellor.Property.Obnam: installed :: Property NoInfo
+ Propellor.Property.Obnam: installed :: Property DebianLike
- Propellor.Property.Obnam: restored :: FilePath -> [ObnamParam] -> Property NoInfo
+ Propellor.Property.Obnam: restored :: FilePath -> [ObnamParam] -> Property DebianLike
- Propellor.Property.OpenId: providerFor :: [User] -> HostName -> Maybe Port -> Property HasInfo
+ Propellor.Property.OpenId: providerFor :: [User] -> HostName -> Maybe Port -> Property (HasInfo + DebianLike)
- Propellor.Property.Parted: installed :: Property NoInfo
+ Propellor.Property.Parted: installed :: Property DebianLike
- Propellor.Property.Parted: parted :: Eep -> FilePath -> [String] -> Property NoInfo
+ Propellor.Property.Parted: parted :: Eep -> FilePath -> [String] -> Property DebianLike
- Propellor.Property.Parted: partitioned :: Eep -> FilePath -> PartTable -> Property NoInfo
+ Propellor.Property.Parted: partitioned :: Eep -> FilePath -> PartTable -> Property DebianLike
- Propellor.Property.Partition: formatted :: Eep -> Fs -> FilePath -> Property NoInfo
+ Propellor.Property.Partition: formatted :: Eep -> Fs -> FilePath -> Property DebianLike
- Propellor.Property.Partition: formatted' :: MkfsOpts -> Eep -> Fs -> FilePath -> Property NoInfo
+ Propellor.Property.Partition: formatted' :: MkfsOpts -> Eep -> Fs -> FilePath -> Property DebianLike
- Propellor.Property.Partition: kpartx :: FilePath -> ([LoopDev] -> Property NoInfo) -> Property NoInfo
+ Propellor.Property.Partition: kpartx :: FilePath -> ([LoopDev] -> Property DebianLike) -> Property DebianLike
- Propellor.Property.Postfix: dedupMainCf :: Property NoInfo
+ Propellor.Property.Postfix: dedupMainCf :: Property UnixLike
- Propellor.Property.Postfix: installed :: Property NoInfo
+ Propellor.Property.Postfix: installed :: Property DebianLike
- Propellor.Property.Postfix: mainCf :: (String, String) -> Property NoInfo
+ Propellor.Property.Postfix: mainCf :: (String, String) -> Property UnixLike
- Propellor.Property.Postfix: mappedFile :: Combines (Property x) (Property NoInfo) => FilePath -> (FilePath -> Property x) -> Property (CInfo x NoInfo)
+ Propellor.Property.Postfix: mappedFile :: Combines (Property x) (Property UnixLike) => FilePath -> (FilePath -> Property x) -> CombinedType (Property x) (Property UnixLike)
- Propellor.Property.Postfix: newaliases :: Property NoInfo
+ Propellor.Property.Postfix: newaliases :: Property UnixLike
- Propellor.Property.Postfix: reloaded :: Property NoInfo
+ Propellor.Property.Postfix: reloaded :: Property DebianLike
- Propellor.Property.Postfix: restarted :: Property NoInfo
+ Propellor.Property.Postfix: restarted :: Property DebianLike
- Propellor.Property.Postfix: saslAuthdInstalled :: Property NoInfo
+ Propellor.Property.Postfix: saslAuthdInstalled :: Property DebianLike
- Propellor.Property.Postfix: saslPasswdSet :: Domain -> User -> Property HasInfo
+ Propellor.Property.Postfix: saslPasswdSet :: Domain -> User -> Property (HasInfo + UnixLike)
- Propellor.Property.Postfix: satellite :: Property NoInfo
+ Propellor.Property.Postfix: satellite :: Property DebianLike
- Propellor.Property.Postfix: service :: Service -> RevertableProperty NoInfo
+ Propellor.Property.Postfix: service :: Service -> RevertableProperty DebianLike DebianLike
- Propellor.Property.PropellorRepo: hasOriginUrl :: String -> Property NoInfo
+ Propellor.Property.PropellorRepo: hasOriginUrl :: String -> Property UnixLike
- Propellor.Property.Prosody: confAvailable :: Conf -> ConfigFile -> Property NoInfo
+ Propellor.Property.Prosody: confAvailable :: Conf -> ConfigFile -> Property DebianLike
- Propellor.Property.Prosody: confEnabled :: Conf -> ConfigFile -> RevertableProperty NoInfo
+ Propellor.Property.Prosody: confEnabled :: Conf -> ConfigFile -> RevertableProperty DebianLike DebianLike
- Propellor.Property.Prosody: installed :: Property NoInfo
+ Propellor.Property.Prosody: installed :: Property DebianLike
- Propellor.Property.Prosody: reloaded :: Property NoInfo
+ Propellor.Property.Prosody: reloaded :: Property DebianLike
- Propellor.Property.Prosody: restarted :: Property NoInfo
+ Propellor.Property.Prosody: restarted :: Property DebianLike
- Propellor.Property.Reboot: atEnd :: Bool -> (Result -> Bool) -> Property NoInfo
+ Propellor.Property.Reboot: atEnd :: Bool -> (Result -> Bool) -> Property Linux
- Propellor.Property.Reboot: now :: Property NoInfo
+ Propellor.Property.Reboot: now :: Property Linux
- Propellor.Property.Rsync: rsync :: [String] -> Property NoInfo
+ Propellor.Property.Rsync: rsync :: [String] -> Property DebianLike
- Propellor.Property.Rsync: syncDir :: Src -> Dest -> Property NoInfo
+ Propellor.Property.Rsync: syncDir :: Src -> Dest -> Property DebianLike
- Propellor.Property.Rsync: syncDirFiltered :: [Filter] -> Src -> Dest -> Property NoInfo
+ Propellor.Property.Rsync: syncDirFiltered :: [Filter] -> Src -> Dest -> Property DebianLike
- Propellor.Property.Scheduled: periodParse :: Property NoInfo -> String -> Property NoInfo
+ Propellor.Property.Scheduled: periodParse :: (IsProp (Property i)) => Property i -> String -> Property i
- Propellor.Property.Service: reloaded :: ServiceName -> Property NoInfo
+ Propellor.Property.Service: reloaded :: ServiceName -> Property DebianLike
- Propellor.Property.Service: restarted :: ServiceName -> Property NoInfo
+ Propellor.Property.Service: restarted :: ServiceName -> Property DebianLike
- Propellor.Property.Service: running :: ServiceName -> Property NoInfo
+ Propellor.Property.Service: running :: ServiceName -> Property DebianLike
- Propellor.Property.Service: signaled :: String -> Desc -> ServiceName -> Property NoInfo
+ Propellor.Property.Service: signaled :: String -> Desc -> ServiceName -> Property DebianLike
- Propellor.Property.SiteSpecific.Branchable: server :: [Host] -> Property HasInfo
+ Propellor.Property.SiteSpecific.Branchable: server :: [Host] -> Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.GitAnnexBuilder: armAutoBuilder :: System -> Flavor -> Property HasInfo
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: armAutoBuilder :: DebianSuite -> Architecture -> Flavor -> Property (HasInfo + Debian)
- Propellor.Property.SiteSpecific.GitAnnexBuilder: autoBuilderContainer :: (System -> Flavor -> Property HasInfo) -> System -> Flavor -> Times -> TimeOut -> Container
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: autoBuilderContainer :: (DebianSuite -> Architecture -> Flavor -> Property (HasInfo + Debian)) -> DebianSuite -> Architecture -> Flavor -> Times -> TimeOut -> Container
- Propellor.Property.SiteSpecific.GitAnnexBuilder: autobuilder :: Architecture -> Times -> TimeOut -> Property HasInfo
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: autobuilder :: Architecture -> Times -> TimeOut -> Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.GitAnnexBuilder: buildDepsApt :: Property HasInfo
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: buildDepsApt :: Property DebianLike
- Propellor.Property.SiteSpecific.GitAnnexBuilder: buildDepsNoHaskellLibs :: Property NoInfo
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: buildDepsNoHaskellLibs :: Property DebianLike
- Propellor.Property.SiteSpecific.GitAnnexBuilder: cabalDeps :: Property NoInfo
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: cabalDeps :: Property UnixLike
- Propellor.Property.SiteSpecific.GitAnnexBuilder: haskellPkgsInstalled :: String -> Property NoInfo
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: haskellPkgsInstalled :: String -> Property DebianLike
- Propellor.Property.SiteSpecific.GitAnnexBuilder: stackAutoBuilder :: System -> Flavor -> Property HasInfo
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: stackAutoBuilder :: DebianSuite -> Architecture -> Flavor -> Property (HasInfo + Debian)
- Propellor.Property.SiteSpecific.GitAnnexBuilder: stackInstalled :: Property NoInfo
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: stackInstalled :: Property Linux
- Propellor.Property.SiteSpecific.GitAnnexBuilder: standardAutoBuilder :: System -> Flavor -> Property HasInfo
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: standardAutoBuilder :: DebianSuite -> Architecture -> Flavor -> Property (HasInfo + Debian)
- Propellor.Property.SiteSpecific.GitAnnexBuilder: tree :: Architecture -> Flavor -> Property HasInfo
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: tree :: Architecture -> Flavor -> Property DebianLike
- Propellor.Property.SiteSpecific.GitHome: installedFor :: User -> Property NoInfo
+ Propellor.Property.SiteSpecific.GitHome: installedFor :: User -> Property DebianLike
- Propellor.Property.SiteSpecific.IABak: gitServer :: [Host] -> Property HasInfo
+ Propellor.Property.SiteSpecific.IABak: gitServer :: [Host] -> Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.IABak: graphiteServer :: Property HasInfo
+ Propellor.Property.SiteSpecific.IABak: graphiteServer :: Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.IABak: publicFace :: Property HasInfo
+ Propellor.Property.SiteSpecific.IABak: publicFace :: Property DebianLike
- Propellor.Property.SiteSpecific.IABak: registrationServer :: [Host] -> Property HasInfo
+ Propellor.Property.SiteSpecific.IABak: registrationServer :: [Host] -> Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.JoeySites: alarmClock :: String -> User -> String -> Property NoInfo
+ Propellor.Property.SiteSpecific.JoeySites: alarmClock :: String -> User -> String -> Property DebianLike
- Propellor.Property.SiteSpecific.JoeySites: annexWebSite :: RepoUrl -> HostName -> AnnexUUID -> [(String, RepoUrl)] -> Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: annexWebSite :: RepoUrl -> HostName -> AnnexUUID -> [(String, RepoUrl)] -> Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.JoeySites: apacheSite :: HostName -> Bool -> ConfigFile -> RevertableProperty NoInfo
+ Propellor.Property.SiteSpecific.JoeySites: apacheSite :: HostName -> ConfigFile -> RevertableProperty DebianLike DebianLike
- Propellor.Property.SiteSpecific.JoeySites: apachecfg :: HostName -> Bool -> ConfigFile -> ConfigFile
+ Propellor.Property.SiteSpecific.JoeySites: apachecfg :: HostName -> ConfigFile -> ConfigFile
- Propellor.Property.SiteSpecific.JoeySites: backupsBackedupFrom :: [Host] -> HostName -> FilePath -> Property NoInfo
+ Propellor.Property.SiteSpecific.JoeySites: backupsBackedupFrom :: [Host] -> HostName -> FilePath -> Property DebianLike
- Propellor.Property.SiteSpecific.JoeySites: dkimInstalled :: Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: dkimInstalled :: Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.JoeySites: dkimMilter :: Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: dkimMilter :: Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.JoeySites: downloads :: [Host] -> Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: downloads :: [Host] -> Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.JoeySites: gitAnnexDistributor :: Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: gitAnnexDistributor :: Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.JoeySites: gitServer :: [Host] -> Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: gitServer :: [Host] -> Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.JoeySites: githubBackup :: Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: githubBackup :: Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.JoeySites: githubKeys :: Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: githubKeys :: Property (HasInfo + UnixLike)
- Propellor.Property.SiteSpecific.JoeySites: hasJoeyCAChain :: Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: hasJoeyCAChain :: Property (HasInfo + UnixLike)
- Propellor.Property.SiteSpecific.JoeySites: hasPostfixCert :: Context -> Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: hasPostfixCert :: Context -> Property (HasInfo + UnixLike)
- Propellor.Property.SiteSpecific.JoeySites: ircBouncer :: Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: ircBouncer :: Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.JoeySites: kgbServer :: Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: kgbServer :: Property (HasInfo + Debian)
- Propellor.Property.SiteSpecific.JoeySites: kiteMailServer :: Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: kiteMailServer :: Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.JoeySites: kiteShellBox :: Property NoInfo
+ Propellor.Property.SiteSpecific.JoeySites: kiteShellBox :: Property DebianLike
- Propellor.Property.SiteSpecific.JoeySites: legacyWebSites :: Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: legacyWebSites :: Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.JoeySites: mumbleServer :: [Host] -> Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: mumbleServer :: [Host] -> Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.JoeySites: obnamRepos :: [String] -> Property NoInfo
+ Propellor.Property.SiteSpecific.JoeySites: obnamRepos :: [String] -> Property UnixLike
- Propellor.Property.SiteSpecific.JoeySites: oldUseNetInstalled :: Package -> Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: oldUseNetInstalled :: Package -> Property DebianLike
- Propellor.Property.SiteSpecific.JoeySites: oldUseNetServer :: [Host] -> Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: oldUseNetServer :: [Host] -> Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.JoeySites: oldUseNetShellBox :: Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: oldUseNetShellBox :: Property DebianLike
- Propellor.Property.SiteSpecific.JoeySites: podcatcher :: Property NoInfo
+ Propellor.Property.SiteSpecific.JoeySites: podcatcher :: Property DebianLike
- Propellor.Property.SiteSpecific.JoeySites: postfixClientRelay :: Context -> Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: postfixClientRelay :: Context -> Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.JoeySites: pumpRss :: Property NoInfo
+ Propellor.Property.SiteSpecific.JoeySites: pumpRss :: Property DebianLike
- Propellor.Property.SiteSpecific.JoeySites: rsyncNetBackup :: [Host] -> Property NoInfo
+ Propellor.Property.SiteSpecific.JoeySites: rsyncNetBackup :: [Host] -> Property DebianLike
- Propellor.Property.SiteSpecific.JoeySites: scrollBox :: Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: scrollBox :: Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.JoeySites: tmp :: Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: tmp :: Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.JoeySites: twitRss :: Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: twitRss :: Property DebianLike
- Propellor.Property.SiteSpecific.JoeySites: userDirHtml :: Property NoInfo
+ Propellor.Property.SiteSpecific.JoeySites: userDirHtml :: Property DebianLike
- Propellor.Property.Ssh: authorizedKey :: User -> String -> RevertableProperty NoInfo
+ Propellor.Property.Ssh: authorizedKey :: User -> String -> RevertableProperty UnixLike UnixLike
- Propellor.Property.Ssh: authorizedKeys :: IsContext c => User -> c -> Property HasInfo
+ Propellor.Property.Ssh: authorizedKeys :: IsContext c => User -> c -> Property (HasInfo + UnixLike)
- Propellor.Property.Ssh: authorizedKeysFrom :: User -> (User, Host) -> Property NoInfo
+ Propellor.Property.Ssh: authorizedKeysFrom :: User -> (User, Host) -> Property UnixLike
- Propellor.Property.Ssh: hostKey :: IsContext c => c -> SshKeyType -> PubKeyText -> Property HasInfo
+ Propellor.Property.Ssh: hostKey :: IsContext c => c -> SshKeyType -> PubKeyText -> Property (HasInfo + DebianLike)
- Propellor.Property.Ssh: hostKeys :: IsContext c => c -> [(SshKeyType, PubKeyText)] -> Property HasInfo
+ Propellor.Property.Ssh: hostKeys :: IsContext c => c -> [(SshKeyType, PubKeyText)] -> Property (HasInfo + DebianLike)
- Propellor.Property.Ssh: hostPubKey :: SshKeyType -> PubKeyText -> Property HasInfo
+ Propellor.Property.Ssh: hostPubKey :: SshKeyType -> PubKeyText -> Property (HasInfo + UnixLike)
- Propellor.Property.Ssh: installed :: Property NoInfo
+ Propellor.Property.Ssh: installed :: Property UnixLike
- Propellor.Property.Ssh: knownHost :: [Host] -> HostName -> User -> Property NoInfo
+ Propellor.Property.Ssh: knownHost :: [Host] -> HostName -> User -> Property UnixLike
- Propellor.Property.Ssh: listenPort :: Port -> RevertableProperty NoInfo
+ Propellor.Property.Ssh: listenPort :: Port -> RevertableProperty DebianLike DebianLike
- Propellor.Property.Ssh: noPasswords :: Property NoInfo
+ Propellor.Property.Ssh: noPasswords :: Property DebianLike
- Propellor.Property.Ssh: passwordAuthentication :: Bool -> Property NoInfo
+ Propellor.Property.Ssh: passwordAuthentication :: Bool -> Property DebianLike
- Propellor.Property.Ssh: permitRootLogin :: RootLogin -> Property NoInfo
+ Propellor.Property.Ssh: permitRootLogin :: RootLogin -> Property DebianLike
- Propellor.Property.Ssh: randomHostKeys :: Property NoInfo
+ Propellor.Property.Ssh: randomHostKeys :: Property DebianLike
- Propellor.Property.Ssh: restarted :: Property NoInfo
+ Propellor.Property.Ssh: restarted :: Property DebianLike
- Propellor.Property.Ssh: setSshdConfig :: ConfigKeyword -> String -> Property NoInfo
+ Propellor.Property.Ssh: setSshdConfig :: ConfigKeyword -> String -> Property DebianLike
- Propellor.Property.Ssh: setSshdConfigBool :: ConfigKeyword -> Bool -> Property NoInfo
+ Propellor.Property.Ssh: setSshdConfigBool :: ConfigKeyword -> Bool -> Property DebianLike
- Propellor.Property.Ssh: unauthorizedKeysFrom :: User -> (User, Host) -> Property NoInfo
+ Propellor.Property.Ssh: unauthorizedKeysFrom :: User -> (User, Host) -> Property UnixLike
- Propellor.Property.Ssh: unknownHost :: [Host] -> HostName -> User -> Property NoInfo
+ Propellor.Property.Ssh: unknownHost :: [Host] -> HostName -> User -> Property UnixLike
- Propellor.Property.Ssh: userKeyAt :: IsContext c => Maybe FilePath -> User -> c -> (SshKeyType, PubKeyText) -> Property HasInfo
+ Propellor.Property.Ssh: userKeyAt :: IsContext c => Maybe FilePath -> User -> c -> (SshKeyType, PubKeyText) -> Property (HasInfo + UnixLike)
- Propellor.Property.Ssh: userKeys :: IsContext c => User -> c -> [(SshKeyType, PubKeyText)] -> Property HasInfo
+ Propellor.Property.Ssh: userKeys :: IsContext c => User -> c -> [(SshKeyType, PubKeyText)] -> Property (HasInfo + UnixLike)
- Propellor.Property.Sudo: enabledFor :: User -> Property NoInfo
+ Propellor.Property.Sudo: enabledFor :: User -> Property DebianLike
- Propellor.Property.Systemd: bind :: Bindable p => p -> RevertableProperty HasInfo
+ Propellor.Property.Systemd: bind :: Bindable p => p -> RevertableProperty (HasInfo + Linux) (HasInfo + Linux)
- Propellor.Property.Systemd: bindRo :: Bindable p => p -> RevertableProperty HasInfo
+ Propellor.Property.Systemd: bindRo :: Bindable p => p -> RevertableProperty (HasInfo + Linux) (HasInfo + Linux)
- Propellor.Property.Systemd: configured :: FilePath -> Option -> String -> Property NoInfo
+ Propellor.Property.Systemd: configured :: FilePath -> Option -> String -> Property Linux
- Propellor.Property.Systemd: container :: MachineName -> System -> (FilePath -> Chroot) -> Container
+ Propellor.Property.Systemd: container :: MachineName -> (FilePath -> Chroot) -> Container
- Propellor.Property.Systemd: containerCfg :: String -> RevertableProperty HasInfo
+ Propellor.Property.Systemd: containerCfg :: String -> RevertableProperty (HasInfo + Linux) (HasInfo + Linux)
- Propellor.Property.Systemd: daemonReloaded :: Property NoInfo
+ Propellor.Property.Systemd: daemonReloaded :: Property Linux
- Propellor.Property.Systemd: disabled :: ServiceName -> Property NoInfo
+ Propellor.Property.Systemd: disabled :: ServiceName -> Property Linux
- Propellor.Property.Systemd: enabled :: ServiceName -> Property NoInfo
+ Propellor.Property.Systemd: enabled :: ServiceName -> Property Linux
- Propellor.Property.Systemd: installed :: Property NoInfo
+ Propellor.Property.Systemd: installed :: Property DebianLike
- Propellor.Property.Systemd: journaldConfigured :: Option -> String -> Property NoInfo
+ Propellor.Property.Systemd: journaldConfigured :: Option -> String -> Property Linux
- Propellor.Property.Systemd: linkJournal :: RevertableProperty HasInfo
+ Propellor.Property.Systemd: linkJournal :: RevertableProperty (HasInfo + Linux) (HasInfo + Linux)
- Propellor.Property.Systemd: machined :: Property NoInfo
+ Propellor.Property.Systemd: machined :: Property Linux
- Propellor.Property.Systemd: masked :: ServiceName -> RevertableProperty NoInfo
+ Propellor.Property.Systemd: masked :: ServiceName -> RevertableProperty Linux Linux
- Propellor.Property.Systemd: nspawned :: Container -> RevertableProperty HasInfo
+ Propellor.Property.Systemd: nspawned :: Container -> RevertableProperty (HasInfo + Linux) Linux
- Propellor.Property.Systemd: persistentJournal :: Property NoInfo
+ Propellor.Property.Systemd: persistentJournal :: Property DebianLike
- Propellor.Property.Systemd: privateNetwork :: RevertableProperty HasInfo
+ Propellor.Property.Systemd: privateNetwork :: RevertableProperty (HasInfo + Linux) (HasInfo + Linux)
- Propellor.Property.Systemd: publish :: Publishable p => p -> RevertableProperty HasInfo
+ Propellor.Property.Systemd: publish :: Publishable p => p -> RevertableProperty (HasInfo + Linux) (HasInfo + Linux)
- Propellor.Property.Systemd: resolvConfed :: RevertableProperty HasInfo
+ Propellor.Property.Systemd: resolvConfed :: RevertableProperty (HasInfo + Linux) (HasInfo + Linux)
- Propellor.Property.Systemd: restarted :: ServiceName -> Property NoInfo
+ Propellor.Property.Systemd: restarted :: ServiceName -> Property Linux
- Propellor.Property.Systemd: running :: ServiceName -> Property NoInfo
+ Propellor.Property.Systemd: running :: ServiceName -> Property Linux
- Propellor.Property.Systemd: started :: ServiceName -> Property NoInfo
+ Propellor.Property.Systemd: started :: ServiceName -> Property Linux
- Propellor.Property.Systemd: stopped :: ServiceName -> Property NoInfo
+ Propellor.Property.Systemd: stopped :: ServiceName -> Property Linux
- Propellor.Property.Systemd.Core: installed :: Property NoInfo
+ Propellor.Property.Systemd.Core: installed :: Property DebianLike
- Propellor.Property.Tor: bandwidthRate :: BwLimit -> Property NoInfo
+ Propellor.Property.Tor: bandwidthRate :: BwLimit -> Property DebianLike
- Propellor.Property.Tor: bandwidthRate' :: String -> Integer -> Property NoInfo
+ Propellor.Property.Tor: bandwidthRate' :: String -> Integer -> Property DebianLike
- Propellor.Property.Tor: configured :: [(String, String)] -> Property NoInfo
+ Propellor.Property.Tor: configured :: [(String, String)] -> Property DebianLike
- Propellor.Property.Tor: hiddenService :: HiddenServiceName -> Int -> Property NoInfo
+ Propellor.Property.Tor: hiddenService :: HiddenServiceName -> Int -> Property DebianLike
- Propellor.Property.Tor: hiddenServiceAvailable :: HiddenServiceName -> Int -> Property NoInfo
+ Propellor.Property.Tor: hiddenServiceAvailable :: HiddenServiceName -> Int -> Property DebianLike
- Propellor.Property.Tor: hiddenServiceData :: IsContext c => HiddenServiceName -> c -> Property HasInfo
+ Propellor.Property.Tor: hiddenServiceData :: IsContext c => HiddenServiceName -> c -> Property (HasInfo + DebianLike)
- Propellor.Property.Tor: installed :: Property NoInfo
+ Propellor.Property.Tor: installed :: Property DebianLike
- Propellor.Property.Tor: isBridge :: Property NoInfo
+ Propellor.Property.Tor: isBridge :: Property DebianLike
- Propellor.Property.Tor: isRelay :: Property NoInfo
+ Propellor.Property.Tor: isRelay :: Property DebianLike
- Propellor.Property.Tor: named :: NodeName -> Property HasInfo
+ Propellor.Property.Tor: named :: NodeName -> Property (HasInfo + DebianLike)
- Propellor.Property.Tor: restarted :: Property NoInfo
+ Propellor.Property.Tor: restarted :: Property DebianLike
- Propellor.Property.Tor: server :: Property NoInfo
+ Propellor.Property.Tor: server :: Property DebianLike
- Propellor.Property.Tor: torPrivKey :: Context -> Property HasInfo
+ Propellor.Property.Tor: torPrivKey :: Context -> Property (HasInfo + DebianLike)
- Propellor.Property.Tor: torPrivKeyDirExists :: Property NoInfo
+ Propellor.Property.Tor: torPrivKeyDirExists :: Property DebianLike
- Propellor.Property.Unbound: cachingDnsServer :: [UnboundSection] -> [UnboundZone] -> [UnboundHost] -> Property NoInfo
+ Propellor.Property.Unbound: cachingDnsServer :: [UnboundSection] -> [UnboundZone] -> [UnboundHost] -> Property DebianLike
- Propellor.Property.Unbound: installed :: Property NoInfo
+ Propellor.Property.Unbound: installed :: Property DebianLike
- Propellor.Property.Unbound: reloaded :: Property NoInfo
+ Propellor.Property.Unbound: reloaded :: Property DebianLike
- Propellor.Property.Unbound: restarted :: Property NoInfo
+ Propellor.Property.Unbound: restarted :: Property DebianLike
- Propellor.Property.User: accountFor :: User -> Property NoInfo
+ Propellor.Property.User: accountFor :: User -> Property DebianLike
- Propellor.Property.User: hasDesktopGroups :: User -> Property NoInfo
+ Propellor.Property.User: hasDesktopGroups :: User -> Property DebianLike
- Propellor.Property.User: hasGroup :: User -> Group -> Property NoInfo
+ Propellor.Property.User: hasGroup :: User -> Group -> Property DebianLike
- Propellor.Property.User: hasInsecurePassword :: User -> String -> Property NoInfo
+ Propellor.Property.User: hasInsecurePassword :: User -> String -> Property DebianLike
- Propellor.Property.User: hasLoginShell :: User -> FilePath -> Property NoInfo
+ Propellor.Property.User: hasLoginShell :: User -> FilePath -> Property DebianLike
- Propellor.Property.User: hasPassword :: User -> Property HasInfo
+ Propellor.Property.User: hasPassword :: User -> Property (HasInfo + DebianLike)
- Propellor.Property.User: hasPassword' :: IsContext c => User -> c -> Property HasInfo
+ Propellor.Property.User: hasPassword' :: IsContext c => User -> c -> Property (HasInfo + DebianLike)
- Propellor.Property.User: hasSomePassword :: User -> Property HasInfo
+ Propellor.Property.User: hasSomePassword :: User -> Property (HasInfo + DebianLike)
- Propellor.Property.User: hasSomePassword' :: IsContext c => User -> c -> Property HasInfo
+ Propellor.Property.User: hasSomePassword' :: IsContext c => User -> c -> Property (HasInfo + DebianLike)
- Propellor.Property.User: lockedPassword :: User -> Property NoInfo
+ Propellor.Property.User: lockedPassword :: User -> Property DebianLike
- Propellor.Property.User: nuked :: User -> Eep -> Property NoInfo
+ Propellor.Property.User: nuked :: User -> Eep -> Property DebianLike
- Propellor.Property.User: shadowConfig :: Bool -> Property NoInfo
+ Propellor.Property.User: shadowConfig :: Bool -> Property DebianLike
- Propellor.Property.User: shellEnabled :: FilePath -> Property NoInfo
+ Propellor.Property.User: shellEnabled :: FilePath -> Property DebianLike
- Propellor.Property.User: shellSetTo :: User -> FilePath -> Property NoInfo
+ Propellor.Property.User: shellSetTo :: User -> FilePath -> Property DebianLike
- Propellor.Property.User: systemAccountFor :: User -> Property NoInfo
+ Propellor.Property.User: systemAccountFor :: User -> Property DebianLike
- Propellor.Property.User: systemAccountFor' :: User -> Maybe FilePath -> Maybe Group -> Property NoInfo
+ Propellor.Property.User: systemAccountFor' :: User -> Maybe FilePath -> Maybe Group -> Property DebianLike
- Propellor.Property.Uwsgi: appAvailable :: AppName -> ConfigFile -> Property NoInfo
+ Propellor.Property.Uwsgi: appAvailable :: AppName -> ConfigFile -> Property DebianLike
- Propellor.Property.Uwsgi: appEnabled :: AppName -> ConfigFile -> RevertableProperty NoInfo
+ Propellor.Property.Uwsgi: appEnabled :: AppName -> ConfigFile -> RevertableProperty DebianLike DebianLike
- Propellor.Property.Uwsgi: installed :: Property NoInfo
+ Propellor.Property.Uwsgi: installed :: Property DebianLike
- Propellor.Property.Uwsgi: reloaded :: Property NoInfo
+ Propellor.Property.Uwsgi: reloaded :: Property DebianLike
- Propellor.Property.Uwsgi: restarted :: Property NoInfo
+ Propellor.Property.Uwsgi: restarted :: Property DebianLike
- Propellor.Property.ZFS.Properties: zfsExists :: ZFS -> Property NoInfo
+ Propellor.Property.ZFS.Properties: zfsExists :: ZFS -> Property ZFSOS
- Propellor.Property.ZFS.Properties: zfsSetProperties :: ZFS -> ZFSProperties -> Property NoInfo
+ Propellor.Property.ZFS.Properties: zfsSetProperties :: ZFS -> ZFSProperties -> Property ZFSOS
- Propellor.Types: (<!>) :: MkRevertableProperty i1 i2 => Property i1 -> Property i2 -> RevertableProperty (CInfo i1 i2)
+ Propellor.Types: (<!>) :: Property setupmetatypes -> Property undometatypes -> RevertableProperty setupmetatypes undometatypes
- Propellor.Types: Host :: HostName -> [Property HasInfo] -> Info -> Host
+ Propellor.Types: Host :: HostName -> [ChildProperty] -> Info -> Host
- Propellor.Types: RevertableProperty :: Property i -> Property i -> RevertableProperty i
+ Propellor.Types: RevertableProperty :: Property setupmetatypes -> Property undometatypes -> RevertableProperty setupmetatypes undometatypes
- Propellor.Types: [hostProperties] :: Host -> [Property HasInfo]
+ Propellor.Types: [hostProperties] :: Host -> [ChildProperty]
- Propellor.Types: [setupRevertableProperty] :: RevertableProperty i -> Property i
+ Propellor.Types: [setupRevertableProperty] :: RevertableProperty setupmetatypes undometatypes -> Property setupmetatypes
- Propellor.Types: [undoRevertableProperty] :: RevertableProperty i -> Property i
+ Propellor.Types: [undoRevertableProperty] :: RevertableProperty setupmetatypes undometatypes -> Property undometatypes
- Propellor.Types: adjustPropertySatisfy :: Property i -> (Propellor Result -> Propellor Result) -> Property i
+ Propellor.Types: adjustPropertySatisfy :: Property metatypes -> (Propellor Result -> Propellor Result) -> Property metatypes
- Propellor.Types: data Property i
+ Propellor.Types: data Property metatypes
- Propellor.Types: data RevertableProperty i
+ Propellor.Types: data RevertableProperty setupmetatypes undometatypes
Files
- CHANGELOG +79/−2
- README.md +8/−13
- config-freebsd.hs +6/−7
- config-simple.hs +2/−21
- config.hs +2/−21
- debian/changelog +79/−2
- debian/control +2/−0
- doc/README.mdwn +8/−13
- joeyconfig.hs +78/−72
- propellor.cabal +31/−18
- src/Propellor.hs +5/−4
- src/Propellor/Bootstrap.hs +50/−12
- src/Propellor/CmdLine.hs +7/−0
- src/Propellor/Container.hs +62/−0
- src/Propellor/DotDir.hs +393/−0
- src/Propellor/Engine.hs +12/−11
- src/Propellor/EnsureProperty.hs +70/−0
- src/Propellor/Gpg.hs +12/−5
- src/Propellor/Info.hs +84/−20
- src/Propellor/PrivData.hs +28/−17
- src/Propellor/PropAccum.hs +60/−62
- src/Propellor/Property.hs +97/−29
- src/Propellor/Property/Aiccu.hs +9/−7
- src/Propellor/Property/Apache.hs +27/−27
- src/Propellor/Property/Apt.hs +60/−64
- src/Propellor/Property/Apt/PPA.hs +115/−0
- src/Propellor/Property/Chroot.hs +50/−47
- src/Propellor/Property/Cmd.hs +5/−5
- src/Propellor/Property/Concurrent.hs +6/−8
- src/Propellor/Property/Conductor.hs +33/−24
- src/Propellor/Property/ConfFile.hs +4/−4
- src/Propellor/Property/Cron.hs +12/−13
- src/Propellor/Property/DebianMirror.hs +9/−11
- src/Propellor/Property/Debootstrap.hs +18/−24
- src/Propellor/Property/DiskImage.hs +53/−39
- src/Propellor/Property/Dns.hs +24/−21
- src/Propellor/Property/DnsSec.hs +7/−5
- src/Propellor/Property/Docker.hs +84/−77
- src/Propellor/Property/Fail2Ban.hs +4/−4
- src/Propellor/Property/File.hs +25/−24
- src/Propellor/Property/Firewall.hs +2/−2
- src/Propellor/Property/FreeBSD/Pkg.hs +10/−7
- src/Propellor/Property/FreeBSD/Poudriere.hs +12/−9
- src/Propellor/Property/Git.hs +12/−11
- src/Propellor/Property/Gpg.hs +3/−2
- src/Propellor/Property/Group.hs +1/−1
- src/Propellor/Property/Grub.hs +21/−18
- src/Propellor/Property/HostingProvider/CloudAtCost.hs +19/−14
- src/Propellor/Property/HostingProvider/DigitalOcean.hs +5/−6
- src/Propellor/Property/HostingProvider/Linode.hs +5/−4
- src/Propellor/Property/Hostname.hs +11/−10
- src/Propellor/Property/Journald.hs +8/−8
- src/Propellor/Property/Kerberos.hs +15/−14
- src/Propellor/Property/LetsEncrypt.hs +4/−3
- src/Propellor/Property/LightDM.hs +2/−4
- src/Propellor/Property/List.hs +40/−67
- src/Propellor/Property/Locale.hs +21/−17
- src/Propellor/Property/Logcheck.hs +2/−2
- src/Propellor/Property/Mount.hs +9/−8
- src/Propellor/Property/Network.hs +21/−17
- src/Propellor/Property/Nginx.hs +7/−7
- src/Propellor/Property/OS.hs +31/−20
- src/Propellor/Property/Obnam.hs +9/−8
- src/Propellor/Property/OpenId.hs +3/−3
- src/Propellor/Property/Parted.hs +8/−9
- src/Propellor/Property/Partition.hs +7/−5
- src/Propellor/Property/Postfix.hs +19/−18
- src/Propellor/Property/PropellorRepo.hs +1/−1
- src/Propellor/Property/Prosody.hs +6/−6
- src/Propellor/Property/Reboot.hs +3/−3
- src/Propellor/Property/Rsync.hs +3/−3
- src/Propellor/Property/Scheduled.hs +7/−6
- src/Propellor/Property/Service.hs +5/−5
- src/Propellor/Property/SiteSpecific/Branchable.hs +1/−1
- src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs +54/−49
- src/Propellor/Property/SiteSpecific/GitHome.hs +6/−5
- src/Propellor/Property/SiteSpecific/IABak.hs +7/−6
- src/Propellor/Property/SiteSpecific/JoeySites.hs +101/−130
- src/Propellor/Property/Ssh.hs +105/−88
- src/Propellor/Property/Sudo.hs +5/−4
- src/Propellor/Property/Systemd.hs +96/−68
- src/Propellor/Property/Systemd/Core.hs +1/−1
- src/Propellor/Property/Tor.hs +23/−21
- src/Propellor/Property/Unbound.hs +4/−4
- src/Propellor/Property/User.hs +34/−29
- src/Propellor/Property/Uwsgi.hs +6/−6
- src/Propellor/Property/ZFS/Properties.hs +8/−4
- src/Propellor/Spin.hs +3/−3
- src/Propellor/Types.hs +128/−272
- src/Propellor/Types/CmdLine.hs +1/−0
- src/Propellor/Types/Core.hs +106/−0
- src/Propellor/Types/Info.hs +12/−3
- src/Propellor/Types/MetaTypes.hs +213/−0
- src/Propellor/Types/OS.hs +18/−3
- src/Propellor/Types/ResultCheck.hs +3/−0
- src/Propellor/Types/Singletons.hs +49/−0
- src/System/Console/Concurrent.hs +0/−44
- src/System/Console/Concurrent/Internal.hs +0/−556
- src/System/Process/Concurrent.hs +0/−34
- src/config.hs +2/−21
- src/wrapper.hs +20/−147
- stack.yaml +3/−0
CHANGELOG view
@@ -1,3 +1,80 @@+propellor (3.0.0) unstable; urgency=medium++ * Property types have been improved to indicate what systems they target.+ This prevents using eg, Property FreeBSD on a Debian system.+ Transition guide for this sweeping API change:+ - First, upgrade to propellor 2.17.2 and deploy that to all your hosts.+ Otherwise, propellor --spin will fail when you upgrade to+ propellor 3.0.0.+ - Change "host name & foo & bar"+ to "host name $ props & foo & bar"+ - Similarly, `propertyList` and `combineProperties` need `props`+ to be used to combine together properties; they no longer accept+ lists of properties. (If you have such a list, use `toProps`.)+ - And similarly, Chroot, Docker, and Systemd container need `props`+ to be used to combine together the properies used inside them.+ - The `os` property is removed. Instead use `osDebian`, `osBuntish`,+ or `osFreeBSD`. These tell the type checker the target OS of a host.+ - Change "Property NoInfo" to "Property UnixLike"+ - Change "Property HasInfo" to "Property (HasInfo + UnixLike)"+ - Change "RevertableProperty NoInfo" to+ "RevertableProperty UnixLike UnixLike"+ - Change "RevertableProperty HasInfo" to+ "RevertableProperty (HasInfo + UnixLike) UnixLike"+ - GHC needs {-# LANGUAGE TypeOperators #-} to use these fancy types.+ This is enabled by default for all modules in propellor.cabal. But+ if you are using propellor as a library, you may need to enable it+ manually.+ - If you know a property only works on a particular OS, like Debian+ or FreeBSD, use that instead of "UnixLike". For example:+ "Property Debian"+ - It's also possible make a property support a set of OS's, for example:+ "Property (Debian + FreeBSD)"+ - Removed `infoProperty` and `simpleProperty` constructors, instead use+ `property` to construct a Property.+ - Due to the polymorphic type returned by `property`, additional type+ signatures tend to be needed when using it. For example, this will+ fail to type check, because the type checker cannot guess what type+ you intend the intermediate property "go" to have:+ foo :: Property UnixLike+ foo = go `requires` bar+ where+ go = property "foo" (return NoChange)+ To fix, specify the type of go:+ go :: Property UnixLike+ - `ensureProperty` now needs to be passed a witness to the type of the + property it's used in.+ change this: foo = property desc $ ... ensureProperty bar+ to this: foo = property' desc $ \w -> ... ensureProperty w bar+ - General purpose properties like cmdProperty have type "Property UnixLike".+ When using that to run a command only available on Debian, you can+ tighten the type to only the OS that your more specific property works on.+ For example:+ upgraded :: Property Debian+ upgraded = tightenTargets (cmdProperty "apt-get" ["upgrade"])+ - Several utility functions have been renamed:+ getInfo to fromInfo+ propertyInfo to getInfo+ propertyDesc to getDesc+ propertyChildren to getChildren+ * The new `pickOS` property combinator can be used to combine different+ properties, supporting different OS's, into one Property that chooses+ which to use based on the Host's OS.+ * Re-enabled -O0 in propellor.cabal to reign in ghc's memory use handling+ these complex new types.+ * Added dependency on concurrent-output; removed embedded copy.+ * Apt.PPA: New module, contributed by Evan Cofsky.+ * Improved propellor's first run experience; propellor --init will+ walk the user through setting up ~/.propellor, with a choice between+ a clone of propellor's git repository, or a minimal config, and will+ configure propellor to use a gpg key.+ * Stack support. "git config propellor.buildsystem stack" will make+ propellor build its config using stack.+ * When propellor is installed using stack, propellor --init will+ automatically set propellor.buildsystem=stack.++ -- Joey Hess <id@joeyh.name> Sat, 02 Apr 2016 15:33:26 -0400+ propellor (2.17.2) unstable; urgency=medium * When new dependencies are added to propellor or the propellor config,@@ -492,12 +569,12 @@ This was done to make sure that ensureProperty is only used on properties that do not have Info. Transition guide:- - Change all "Property" to "Property NoInfo" or "Property WithInfo"+ - Change all "Property" to "Property NoInfo" or "Property HasInfo" (The compiler can tell you if you got it wrong!) - To construct a RevertableProperty, it is useful to use the new (<!>) operator - Constructing a list of properties can be problimatic, since- Property NoInto and Property WithInfo are different types and cannot+ Property NoInto and Property HasInfo are different types and cannot appear in the same list. To deal with this, "props" has been added, and can built up a list of properties of different types, using the same (&) and (!) operators that are used to build
README.md view
@@ -42,20 +42,15 @@ `cabal install propellor` or `apt-get install propellor`-2. Run `propellor` for the first time. It will set up a `~/.propellor/` git+2. Run `propellor --init` ; this will set up a `~/.propellor/` git repository for you.-3. If you don't have a gpg private key already, generate one: `gpg --gen-key`-4. Run: `propellor --add-key $KEYID`, which will make propellor trust- your gpg key, and will sign your `~/.propellor` repository using it.-5. Edit `~/.propellor/config.hs`, and add a host you want to manage.+3. Edit `~/.propellor/config.hs`, and add a host you want to manage. You can start by not adding any properties, or only a few.-6. Run: `propellor --spin $HOST`-7. Now you have a simple propellor deployment, but it doesn't do- much to the host yet, besides installing propellor. - 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. Once you have a lot of hosts, and running `propellor --spin HOST` for+4. Run: `propellor --spin $HOST`+5. Now you have a simple propellor deployment to a host. Continue editing+ `~/.propellor/config.hs` to further configure the host, add more hosts+ etc, and re-run `propellor --spin $HOST` after each change. +6. 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!+7. Write some neat new properties and send patches!
config-freebsd.hs view
@@ -27,8 +27,8 @@ -- An example freebsd host. freebsdbox :: Host-freebsdbox = host "freebsdbox.example.com"- & os (System (FreeBSD (FBSDProduction FBSD102)) "amd64")+freebsdbox = host "freebsdbox.example.com" $ props+ & osFreeBSD (FBSDProduction FBSD102) "amd64" & Pkg.update & Pkg.upgrade & Poudriere.poudriere poudriereZFS@@ -43,8 +43,8 @@ -- An example linux host. linuxbox :: Host-linuxbox = host "linuxbox.example.com"- & os (System (Debian Unstable) "amd64")+linuxbox = host "linuxbox.example.com" $ props+ & osDebian Unstable "amd64" & Apt.stdSourcesList & Apt.unattendedUpgrades & Apt.installed ["etckeeper"]@@ -58,10 +58,9 @@ -- A generic webserver in a Docker container. webserverContainer :: Docker.Container-webserverContainer = Docker.container "webserver" (Docker.latestImage "debian")- & os (System (Debian (Stable "jessie")) "amd64")+webserverContainer = Docker.container "webserver" (Docker.latestImage "debian") $ props+ & osDebian (Stable "jessie") "amd64" & Apt.stdSourcesList & Docker.publish "80:80" & Docker.volume "/var/www:/var/www" & Apt.serviceInstalledRunning "apache2"-
config-simple.hs view
@@ -4,15 +4,8 @@ import Propellor import qualified Propellor.Property.File as File import qualified Propellor.Property.Apt as Apt-import qualified Propellor.Property.Network as Network---import qualified Propellor.Property.Ssh as Ssh import qualified Propellor.Property.Cron as Cron-import Propellor.Property.Scheduled---import qualified Propellor.Property.Sudo as Sudo import qualified Propellor.Property.User as User---import qualified Propellor.Property.Hostname as Hostname---import qualified Propellor.Property.Tor as Tor-import qualified Propellor.Property.Docker as Docker main :: IO () main = defaultMain hosts@@ -25,24 +18,12 @@ -- An example host. mybox :: Host-mybox = host "mybox.example.com"- & os (System (Debian Unstable) "amd64")+mybox = host "mybox.example.com" $ props+ & osDebian Unstable "amd64" & Apt.stdSourcesList & Apt.unattendedUpgrades & Apt.installed ["etckeeper"] & Apt.installed ["ssh"] & User.hasSomePassword (User "root")- & Network.ipv6to4 & File.dirExists "/var/www"- & Docker.docked webserverContainer- & Docker.garbageCollected `period` Daily & Cron.runPropellor (Cron.Times "30 * * * *")---- A generic webserver in a Docker container.-webserverContainer :: Docker.Container-webserverContainer = Docker.container "webserver" (Docker.latestImage "debian")- & os (System (Debian (Stable "jessie")) "amd64")- & Apt.stdSourcesList- & Docker.publish "80:80"- & Docker.volume "/var/www:/var/www"- & Apt.serviceInstalledRunning "apache2"
config.hs view
@@ -4,15 +4,8 @@ import Propellor import qualified Propellor.Property.File as File import qualified Propellor.Property.Apt as Apt-import qualified Propellor.Property.Network as Network---import qualified Propellor.Property.Ssh as Ssh import qualified Propellor.Property.Cron as Cron-import Propellor.Property.Scheduled---import qualified Propellor.Property.Sudo as Sudo import qualified Propellor.Property.User as User---import qualified Propellor.Property.Hostname as Hostname---import qualified Propellor.Property.Tor as Tor-import qualified Propellor.Property.Docker as Docker main :: IO () main = defaultMain hosts@@ -25,24 +18,12 @@ -- An example host. mybox :: Host-mybox = host "mybox.example.com"- & os (System (Debian Unstable) "amd64")+mybox = host "mybox.example.com" $ props+ & osDebian Unstable "amd64" & Apt.stdSourcesList & Apt.unattendedUpgrades & Apt.installed ["etckeeper"] & Apt.installed ["ssh"] & User.hasSomePassword (User "root")- & Network.ipv6to4 & File.dirExists "/var/www"- & Docker.docked webserverContainer- & Docker.garbageCollected `period` Daily & Cron.runPropellor (Cron.Times "30 * * * *")---- A generic webserver in a Docker container.-webserverContainer :: Docker.Container-webserverContainer = Docker.container "webserver" (Docker.latestImage "debian")- & os (System (Debian (Stable "jessie")) "amd64")- & Apt.stdSourcesList- & Docker.publish "80:80"- & Docker.volume "/var/www:/var/www"- & Apt.serviceInstalledRunning "apache2"
debian/changelog view
@@ -1,3 +1,80 @@+propellor (3.0.0) unstable; urgency=medium++ * Property types have been improved to indicate what systems they target.+ This prevents using eg, Property FreeBSD on a Debian system.+ Transition guide for this sweeping API change:+ - First, upgrade to propellor 2.17.2 and deploy that to all your hosts.+ Otherwise, propellor --spin will fail when you upgrade to+ propellor 3.0.0.+ - Change "host name & foo & bar"+ to "host name $ props & foo & bar"+ - Similarly, `propertyList` and `combineProperties` need `props`+ to be used to combine together properties; they no longer accept+ lists of properties. (If you have such a list, use `toProps`.)+ - And similarly, Chroot, Docker, and Systemd container need `props`+ to be used to combine together the properies used inside them.+ - The `os` property is removed. Instead use `osDebian`, `osBuntish`,+ or `osFreeBSD`. These tell the type checker the target OS of a host.+ - Change "Property NoInfo" to "Property UnixLike"+ - Change "Property HasInfo" to "Property (HasInfo + UnixLike)"+ - Change "RevertableProperty NoInfo" to+ "RevertableProperty UnixLike UnixLike"+ - Change "RevertableProperty HasInfo" to+ "RevertableProperty (HasInfo + UnixLike) UnixLike"+ - GHC needs {-# LANGUAGE TypeOperators #-} to use these fancy types.+ This is enabled by default for all modules in propellor.cabal. But+ if you are using propellor as a library, you may need to enable it+ manually.+ - If you know a property only works on a particular OS, like Debian+ or FreeBSD, use that instead of "UnixLike". For example:+ "Property Debian"+ - It's also possible make a property support a set of OS's, for example:+ "Property (Debian + FreeBSD)"+ - Removed `infoProperty` and `simpleProperty` constructors, instead use+ `property` to construct a Property.+ - Due to the polymorphic type returned by `property`, additional type+ signatures tend to be needed when using it. For example, this will+ fail to type check, because the type checker cannot guess what type+ you intend the intermediate property "go" to have:+ foo :: Property UnixLike+ foo = go `requires` bar+ where+ go = property "foo" (return NoChange)+ To fix, specify the type of go:+ go :: Property UnixLike+ - `ensureProperty` now needs to be passed a witness to the type of the + property it's used in.+ change this: foo = property desc $ ... ensureProperty bar+ to this: foo = property' desc $ \w -> ... ensureProperty w bar+ - General purpose properties like cmdProperty have type "Property UnixLike".+ When using that to run a command only available on Debian, you can+ tighten the type to only the OS that your more specific property works on.+ For example:+ upgraded :: Property Debian+ upgraded = tightenTargets (cmdProperty "apt-get" ["upgrade"])+ - Several utility functions have been renamed:+ getInfo to fromInfo+ propertyInfo to getInfo+ propertyDesc to getDesc+ propertyChildren to getChildren+ * The new `pickOS` property combinator can be used to combine different+ properties, supporting different OS's, into one Property that chooses+ which to use based on the Host's OS.+ * Re-enabled -O0 in propellor.cabal to reign in ghc's memory use handling+ these complex new types.+ * Added dependency on concurrent-output; removed embedded copy.+ * Apt.PPA: New module, contributed by Evan Cofsky.+ * Improved propellor's first run experience; propellor --init will+ walk the user through setting up ~/.propellor, with a choice between+ a clone of propellor's git repository, or a minimal config, and will+ configure propellor to use a gpg key.+ * Stack support. "git config propellor.buildsystem stack" will make+ propellor build its config using stack.+ * When propellor is installed using stack, propellor --init will+ automatically set propellor.buildsystem=stack.++ -- Joey Hess <id@joeyh.name> Sat, 02 Apr 2016 15:33:26 -0400+ propellor (2.17.2) unstable; urgency=medium * When new dependencies are added to propellor or the propellor config,@@ -492,12 +569,12 @@ This was done to make sure that ensureProperty is only used on properties that do not have Info. Transition guide:- - Change all "Property" to "Property NoInfo" or "Property WithInfo"+ - Change all "Property" to "Property NoInfo" or "Property HasInfo" (The compiler can tell you if you got it wrong!) - To construct a RevertableProperty, it is useful to use the new (<!>) operator - Constructing a list of properties can be problimatic, since- Property NoInto and Property WithInfo are different types and cannot+ Property NoInto and Property HasInfo are different types and cannot appear in the same list. To deal with this, "props" has been added, and can built up a list of properties of different types, using the same (&) and (!) operators that are used to build
debian/control view
@@ -18,6 +18,7 @@ libghc-exceptions-dev (>= 0.6), libghc-stm-dev, libghc-text-dev,+ libghc-concurrent-output-dev, Maintainer: Joey Hess <id@joeyh.name> Standards-Version: 3.9.6 Vcs-Git: git://git.joeyh.name/propellor@@ -41,6 +42,7 @@ libghc-exceptions-dev (>= 0.6), libghc-stm-dev, libghc-text-dev,+ libghc-concurrent-output-dev, git, make, Description: property-based host configuration management in haskell
doc/README.mdwn view
@@ -42,20 +42,15 @@ `cabal install propellor` or `apt-get install propellor`-2. Run `propellor` for the first time. It will set up a `~/.propellor/` git+2. Run `propellor --init` ; this will set up a `~/.propellor/` git repository for you.-3. If you don't have a gpg private key already, generate one: `gpg --gen-key`-4. Run: `propellor --add-key $KEYID`, which will make propellor trust- your gpg key, and will sign your `~/.propellor` repository using it.-5. Edit `~/.propellor/config.hs`, and add a host you want to manage.+3. Edit `~/.propellor/config.hs`, and add a host you want to manage. You can start by not adding any properties, or only a few.-6. Run: `propellor --spin $HOST`-7. Now you have a simple propellor deployment, but it doesn't do- much to the host yet, besides installing propellor. - 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. Once you have a lot of hosts, and running `propellor --spin HOST` for+4. Run: `propellor --spin $HOST`+5. Now you have a simple propellor deployment to a host. Continue editing+ `~/.propellor/config.hs` to further configure the host, add more hosts+ etc, and re-run `propellor --spin $HOST` after each change. +6. 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!+7. Write some neat new properties and send patches!
joeyconfig.hs view
@@ -43,7 +43,7 @@ {- Propellor -- \ / | / ) _.-"-._ Deployed -} -- `/-==__ _/__|/__=-| ( \_ hosts :: [Host] -- * \ | | '--------'-hosts = -- (o) `+hosts = -- (o) ` [ darkstar , gnu , clam@@ -59,24 +59,26 @@ ] ++ monsters testvm :: Host-testvm = host "testvm.kitenet.net"- & os (System (Debian Unstable) "amd64")+testvm = host "testvm.kitenet.net" $ props+ & osDebian Unstable "amd64" & OS.cleanInstallOnce (OS.Confirmed "testvm.kitenet.net")- `onChange` propertyList "fixing up after clean install"- [ OS.preserveRootSshAuthorized- , OS.preserveResolvConf- , Apt.update- , Grub.boots "/dev/sda"- `requires` Grub.installed Grub.PC- ]+ `onChange` postinstall & Hostname.sane & Hostname.searchDomain & Apt.installed ["linux-image-amd64"] & Apt.installed ["ssh"] & User.hasPassword (User "root")+ where+ postinstall :: Property DebianLike+ postinstall = propertyList "fixing up after clean install" $ props+ & OS.preserveRootSshAuthorized+ & OS.preserveResolvConf+ & Apt.update+ & Grub.boots "/dev/sda"+ `requires` Grub.installed Grub.PC darkstar :: Host-darkstar = host "darkstar.kitenet.net"+darkstar = host "darkstar.kitenet.net" $ props & ipv6 "2001:4830:1600:187::2" & Aiccu.hasConfig "T18376" "JHZ2-SIXXS" @@ -95,22 +97,23 @@ , swapPartition (MegaBytes 256) ] where- c d = Chroot.debootstrapped mempty d- & os (System (Debian Unstable) "amd64")+ c d = Chroot.debootstrapped mempty d $ props+ & osDebian Unstable "amd64" & Hostname.setTo "demo" & Apt.installed ["linux-image-amd64"] & User "root" `User.hasInsecurePassword` "root" gnu :: Host-gnu = host "gnu.kitenet.net"+gnu = host "gnu.kitenet.net" $ props & Apt.buildDep ["git-annex"] `period` Daily & JoeySites.postfixClientRelay (Context "gnu.kitenet.net") & JoeySites.dkimMilter clam :: Host-clam = standardSystem "clam.kitenet.net" Unstable "amd64"- [ "Unreliable server. Anything here may be lost at any time!" ]+clam = host "clam.kitenet.net" $ props+ & standardSystem Unstable "amd64" + ["Unreliable server. Anything here may be lost at any time!" ] & ipv4 "167.88.41.194" & CloudAtCost.decruft@@ -141,8 +144,9 @@ & alias "us.scroll.joeyh.name" mayfly :: Host-mayfly = standardSystem "mayfly.kitenet.net" (Stable "jessie") "amd64"- [ "Scratch VM. Contents can change at any time!" ]+mayfly = host "mayfly.kitenet.net" $ props+ & standardSystem (Stable "jessie") "amd64"+ [ "Scratch VM. Contents can change at any time!" ] & ipv4 "167.88.36.193" & CloudAtCost.decruft@@ -156,8 +160,9 @@ & Tor.bandwidthRate (Tor.PerMonth "400 GB") oyster :: Host-oyster = standardSystem "oyster.kitenet.net" Unstable "amd64"- [ "Unreliable server. Anything here may be lost at any time!" ]+oyster = host "oyster.kitenet.net" $ props+ & standardSystem Unstable "amd64"+ [ "Unreliable server. Anything here may be lost at any time!" ] & ipv4 "104.167.117.109" & CloudAtCost.decruft@@ -179,8 +184,8 @@ & Ssh.listenPort (Port 80) orca :: Host-orca = standardSystem "orca.kitenet.net" Unstable "amd64"- [ "Main git-annex build box." ]+orca = host "orca.kitenet.net" $ props+ & standardSystem Unstable "amd64" [ "Main git-annex build box." ] & ipv4 "138.38.108.179" & Apt.unattendedUpgrades@@ -190,19 +195,19 @@ & Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer GitAnnexBuilder.standardAutoBuilder- (System (Debian Unstable) "amd64") Nothing (Cron.Times "15 * * * *") "2h")+ Unstable "amd64" Nothing (Cron.Times "15 * * * *") "2h") & Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer GitAnnexBuilder.standardAutoBuilder- (System (Debian Unstable) "i386") Nothing (Cron.Times "30 * * * *") "2h")+ Unstable "i386" Nothing (Cron.Times "30 * * * *") "2h") & Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer GitAnnexBuilder.stackAutoBuilder- (System (Debian (Stable "jessie")) "i386") (Just "ancient") (Cron.Times "45 * * * *") "2h")+ (Stable "jessie") "i386" (Just "ancient") (Cron.Times "45 * * * *") "2h") & Systemd.nspawned (GitAnnexBuilder.androidAutoBuilderContainer (Cron.Times "1 1 * * *") "3h") honeybee :: Host-honeybee = standardSystem "honeybee.kitenet.net" Testing "armhf"- [ "Arm git-annex build box." ]+honeybee = host "honeybee.kitenet.net" $ props+ & standardSystem Testing "armhf" [ "Arm git-annex build box." ] -- I have to travel to get console access, so no automatic -- upgrades, and try to be robust.@@ -229,14 +234,14 @@ & Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer GitAnnexBuilder.armAutoBuilder- (System (Debian Unstable) "armel") Nothing Cron.Daily "22h")+ Unstable "armel" Nothing Cron.Daily "22h") -- This is not a complete description of kite, since it's a -- multiuser system with eg, user passwords that are not deployed -- with propellor. kite :: Host-kite = standardSystemUnhardened "kite.kitenet.net" Testing "amd64"- [ "Welcome to kite!" ]+kite = host "kite.kitenet.net" $ props+ & standardSystemUnhardened Testing "amd64" [ "Welcome to kite!" ] & ipv4 "66.228.36.95" & ipv6 "2600:3c03::f03c:91ff:fe73:b0d2" & alias "kitenet.net"@@ -292,7 +297,6 @@ & alias "mail.kitenet.net" & JoeySites.kiteMailServer - & JoeySites.kitenetHttps & JoeySites.legacyWebSites & File.ownerGroup "/srv/web" (User "joey") (Group "joey") & Apt.installed ["analog"]@@ -351,10 +355,11 @@ ] elephant :: Host-elephant = standardSystem "elephant.kitenet.net" Unstable "amd64"- [ "Storage, big data, and backups, omnomnom!"- , "(Encrypt all data stored here.)"- ]+elephant = host "elephant.kitenet.net" $ props+ & standardSystem Unstable "amd64"+ [ "Storage, big data, and backups, omnomnom!"+ , "(Encrypt all data stored here.)"+ ] & ipv4 "193.234.225.114" & Ssh.hostKeys hostContext [ (SshDsa, "ssh-dss AAAAB3NzaC1kc3MAAACBANxXGWac0Yz58akI3UbLkphAa8VPDCGswTS0CT3D5xWyL9OeArISAi/OKRIvxA4c+9XnWtNXS7nYVFDJmzzg8v3ZMx543AxXK82kXCfvTOc/nAlVz9YKJAA+FmCloxpmOGrdiTx1k36FE+uQgorslGW/QTxnOcO03fDZej/ppJifAAAAFQCnenyJIw6iJB1+zuF/1TSLT8UAeQAAAIEA1WDrI8rKnxnh2rGaQ0nk+lOcVMLEr7AxParnZjgC4wt2mm/BmkF/feI1Fjft2z4D+V1W7MJHOqshliuproxhFUNGgX9fTbstFJf66p7h7OLAlwK8ZkpRk/uV3h5cIUPel6aCwjL5M2gN6/yq+gcCTXeHLq9OPyUTmlN77SBL71UAAACBAJJiCHWxPAGooe7Vv3W7EIBbsDyf7b2kDH3bsIlo+XFcKIN6jysBu4kn9utjFlrlPeHUDzGQHe+DmSqTUQQ0JPCRGcAcuJL8XUqhJi6A6ye51M9hVt51cJMXmERx9TjLOP/adkEuxpv3Fj20FxRUr1HOmvRvewSHrJ1GeA1bjbYL")@@ -412,7 +417,7 @@ & Ssh.listenPort (Port 80) beaver :: Host-beaver = host "beaver.kitenet.net"+beaver = host "beaver.kitenet.net" $ props & ipv6 "2001:4830:1600:195::2" & Apt.serviceInstalledRunning "aiccu" & Apt.installed ["ssh"]@@ -425,7 +430,7 @@ -- Branchable is not completely deployed with propellor yet. pell :: Host-pell = host "pell.branchable.com"+pell = host "pell.branchable.com" $ props & alias "branchable.com" & ipv4 "66.228.46.55" & ipv6 "2600:3c03::f03c:91ff:fedf:c0e5"@@ -449,10 +454,10 @@ & Branchable.server hosts iabak :: Host-iabak = host "iabak.archiveteam.org"+iabak = host "iabak.archiveteam.org" $ props & ipv4 "124.6.40.227" & Hostname.sane- & os (System (Debian Testing) "amd64")+ & osDebian Testing "amd64" & Systemd.persistentJournal & Cron.runPropellor (Cron.Times "30 * * * *") & Apt.stdSourcesList `onChange` Apt.upgrade@@ -466,7 +471,7 @@ & Apt.installed ["vim", "screen", "tmux", "less", "emax-nox", "netcat"] & User.hasSomePassword (User "root") & propertyList "admin accounts"- (map User.accountFor admins ++ map Sudo.enabledFor admins)+ (toProps $ map User.accountFor admins ++ map Sudo.enabledFor admins) & User.hasSomePassword (User "joey") & GitHome.installedFor (User "joey") & Ssh.authorizedKey (User "db48x") "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAIAQDQ6urXcMDeyuFf4Ga7CuGezTShKnEMPHKJm7RQUtw3yXCPX5wnbvPS2+UFnHMzJvWOX5S5b/XpBpOusP0jLpxwOCEg4nA5b7uvWJ2VIChlMqopYMo+tDOYzK/Q74MZiNWi2hvf1tn3N9SnqOa7muBMKMENIX5KJdH8cJ/BaPqAP883gF8r2SwSZFvaB0xYCT/CIylC593n/+0+Lm07NUJIO8jil3n2SwXdVg6ib65FxZoO86M46wTghnB29GXqrzraOg+5DY1zzCWpIUtFwGr4DP0HqLVtmAkC7NI14l1M0oHE0UEbhoLx/a+mOIMD2DuzW3Rs3ZmHtGLj4PL/eBU8D33AqSeM0uR/0pEcoq6A3a8ixibj9MBYD2lMh+Doa2audxS1OLM//FeNccbm1zlvvde82PZtiO11P98uN+ja4A+CfgQU5s0z0wikc4gXNhWpgvz8DrOEJrjstwOoqkLg2PpIdHRw7dhpp3K1Pc+CGAptDwbKkxs4rzUgMbO9DKI7fPcXXgKHLLShMpmSA2vsQUMfuCp2cVrQJ+Vkbwo29N0Js5yU7L4NL4H854Nbk5uwWJCs/mjXtvTimN2va23HEecTpk44HDUjJ9NyevAfPcO9q1ZtgXFTQSMcdv1m10Fvmnaiy8biHnopL6MBo1VRITh5UFiJYfK4kpTTg2vSspii/FYkkYOAnnZtXZqMehP7OZjJ6HWJpsCVR2hxP3sKOoQu+kcADWa/4obdp+z7gY8iMMjd6kwuIWsNV8KsX+eVJ4UFpAi/L00ZjI2B9QLVCsOg6D1fT0698wEchwUROy5vZZJq0078BdAGnwC0WGLt+7OUgn3O2gUAkb9ffD0odbZSqq96NCelM6RaHA+AaIE4tjGL3lFkyOtb+IGPNACQ73/lmaRQd6Cgasq9cEo0g22Ew5NQi0CBuu1aLDk7ezu3SbU09eB9lcZ+8lFnl5K2eQFeVJStFJbJNfOvgKyOb7ePsrUFF5GJ2J/o1F60fRnG64HizZHxyFWkEOh+k3i8qO+whPa5MTQeYLYb6ysaTPrUwNRcSNNCcPEN8uYOh1dOFAtIYDcYA56BZ321yz0b5umj+pLsrFU+4wMjWxZi0inJzDS4dVegBVcRm0NP5u8VRosJQE9xdbt5K1I0khzhrEW1kowoTbhsZCaDHhL9LZo73Z1WIHvulvlF3RLZip5hhtQu3ZVkbdV5uts8AWaEWVnIu9z0GtQeeOuseZpT0u1/1xjVAOKIzuY3sB7FKOaipe8TDvmdiQf/ICySqqYaYhN6GOhiYccSleoX6yzhYuCvzTgAyWHIfW0t25ff1CM7Vn+Vo9cVplIer1pbwhZZy4QkROWTOE+3yuRlQ+o6op4hTGdAZhjKh9zkDW7rzqQECFrZrX/9mJhxYKjhpkk0X3dSipPt9SUHagc4igya+NgCygQkWBOQfr4uia0LcwDxy4Kchw7ZuypHuGVZkGhNHXS+9JdAHopnSqYwDMG/z1ys1vQihgER0b9g3TchvGF+nmHe2kbM1iuIYMNNlaZD1yGZ5qR7wr/8dw8r0NBEwzsUfak3BUPX7H6X0tGS96llwUxmvQD85WNNoef0uryuAtDEwWlfN1RmWysZDc57Rn4gZi0M5jXmQD23ZiYXYBcG849OeqNzlxONEFsForXO/29Ud4x/Hqa9tf+kJbqMRsaLFO+PXhHzgl6ZHLAljQDxrJ6keNnkqaYfqQ8wyRi1mKv4Ab57kde7mUsZhe7w93GaE9Lxfvu7d3pB+lXfI9NJCSITHreUP4JfmFW+p/eVg+r/1wbElNylGna4I4+qYObOUncGwFKYdFPdtU1XLDKXmywTEgbEh7iI9zX0xD3bPHQLMg+TTtXiU9dQm1x/0zRf9trwDsRDJCbG4/P4iQYkcVvYx2CCfi0JSHv8tWsLi3GJKJLXUxZyzfvY2lThPeYnnY/HFrPJCyJUN55QuRmfzbu8rHgWlcyOlVpKtz+7kn823kEQykiIYKIKrb0G6VBzuMtAk9XzJPv+Wu7suOGXHlVfCqPLk6RjHDm4kTYciW9VgxDts5Y+zwcAbrUeA4UuN/6KisWpivMrfDSIHUCeH/lHBtNkqKohdrUKJMEOx5X6r2dJbmoTFBDi5XtYu/5cBtiDMmupNB0S+pZ2JD5/RKtj6kgzTeE1q/OG4q/eq1O1rjf0vIS31luy27K/YHFIGE0D/CmuXE74Uyaxm27RnrKUxEBl84V70GaIF4F5On8pSThxxizigXTRTKiczc+A5Zi29mid+1EFeUAJOa/DuHJfpVNY4pYEmhPl/Bk66L8kzlbJz6Hg/LIiJIRcy3UKrbSxPFIDpXn33drBHgklMDlrIVDZDXF6cn0Ml71SabB4A3TM6TK+oWZoyvftPIhcWhVwAWQj7nFNAiMEl1z/29ovHrRooqQFozf7GDW8Mjiu7ChZP9zx2H8JB/AAEFuWMwGV4AHICYdS9lOl/v+cDhgsnXdeuKEuxHhYlRxuRxJk/f17Sm/5H85UIzlu85wi3q/DW2FTZnlw4iJLnL6FArUIMzuBOZyoEhh0SPR41Xc4kkucDhnENybTZSR/yDzb0P1B7qjZ4GqcSEFja/hm/LH1oKJzZg8MEqeUoKYCUdVv9ek4IUGUONtVs53V5SOwFWR/nVuDk2BENr7NadYYVtu6MjBwgjso7NuhoNxVwIEP3BW67OQ8bxfNBtJJQNJejAhgZiqJItI9ucAfjQ== db48x@anglachel"@@ -489,14 +494,16 @@ -- Simple web server, publishing the outside host's /var/www webserver :: Systemd.Container-webserver = standardStableContainer "webserver"+webserver = Systemd.debContainer "webserver" $ props+ & standardContainer (Stable "jessie") & Systemd.bind "/var/www" & Apache.installed -- My own openid provider. Uses php, so containerized for security -- and administrative sanity. openidProvider :: Systemd.Container-openidProvider = standardStableContainer "openid-provider"+openidProvider = Systemd.debContainer "openid-provider" $ props+ & standardContainer (Stable "jessie") & alias hn & OpenId.providerFor [User "joey", User "liw"] hn (Just (Port 8081)) where@@ -504,7 +511,8 @@ -- Exhibit: kite's 90's website on port 1994. ancientKitenet :: Systemd.Container-ancientKitenet = standardStableContainer "ancient-kitenet"+ancientKitenet = Systemd.debContainer "ancient-kitenet" $ props+ & standardContainer (Stable "jessie") & alias hn & Git.cloned (User "root") "git://kitenet-net.branchable.com/" "/var/www/html" (Just "remotes/origin/old-kitenet.net")@@ -517,24 +525,27 @@ hn = "ancient.kitenet.net" oldusenetShellBox :: Systemd.Container-oldusenetShellBox = standardStableContainer "oldusenet-shellbox"+oldusenetShellBox = Systemd.debContainer "oldusenet-shellbox" $ props+ & standardContainer (Stable "jessie") & alias "shell.olduse.net" & JoeySites.oldUseNetShellBox kiteShellBox :: Systemd.Container-kiteShellBox = standardStableContainer "kiteshellbox"+kiteShellBox = Systemd.debContainer "kiteshellbox" $ props+ & standardContainer (Stable "jessie") & JoeySites.kiteShellBox type Motd = [String] -- This is my standard system setup.-standardSystem :: HostName -> DebianSuite -> Architecture -> Motd -> Host-standardSystem hn suite arch motd = standardSystemUnhardened hn suite arch motd- & Ssh.noPasswords+standardSystem :: DebianSuite -> Architecture -> Motd -> Property (HasInfo + Debian)+standardSystem suite arch motd = + standardSystemUnhardened suite arch motd+ `before` Ssh.noPasswords -standardSystemUnhardened :: HostName -> DebianSuite -> Architecture -> Motd -> Host-standardSystemUnhardened hn suite arch motd = host hn- & os (System (Debian suite) arch)+standardSystemUnhardened :: DebianSuite -> Architecture -> Motd -> Property (HasInfo + Debian)+standardSystemUnhardened suite arch motd = propertyList "standard system" $ props+ & osDebian suite arch & Hostname.sane & Hostname.searchDomain & File.hasContent "/etc/motd" ("":motd++[""])@@ -555,32 +566,27 @@ `onChange` Apt.autoRemove -- This is my standard container setup, Featuring automatic upgrades.-standardContainer :: Systemd.MachineName -> DebianSuite -> Architecture -> Systemd.Container-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--standardStableContainer :: Systemd.MachineName -> Systemd.Container-standardStableContainer name = standardContainer name (Stable "jessie") "amd64"+standardContainer :: DebianSuite -> Property (HasInfo + Debian)+standardContainer suite = propertyList "standard container" $ props+ & osDebian suite "amd64"+ & Apt.stdSourcesList `onChange` Apt.upgrade+ & Apt.unattendedUpgrades+ & Apt.cacheCleaned -myDnsSecondary :: Property HasInfo+myDnsSecondary :: Property (HasInfo + DebianLike) myDnsSecondary = propertyList "dns secondary for all my domains" $ props & Dns.secondary hosts "kitenet.net" & Dns.secondary hosts "joeyh.name" & Dns.secondary hosts "ikiwiki.info" & Dns.secondary hosts "olduse.net" -branchableSecondary :: RevertableProperty HasInfo+branchableSecondary :: RevertableProperty (HasInfo + DebianLike) DebianLike branchableSecondary = Dns.secondaryFor ["branchable.com"] hosts "branchable.com" -- Currently using kite (ns4) as primary with secondaries -- elephant (ns3) and gandi. -- kite handles all mail.-myDnsPrimary :: Bool -> Domain -> [(BindDomain, Record)] -> RevertableProperty HasInfo+myDnsPrimary :: Bool -> Domain -> [(BindDomain, Record)] -> RevertableProperty (HasInfo + DebianLike) DebianLike myDnsPrimary dnssec domain extras = (if dnssec then Dns.signedPrimary (Weekly Nothing) else Dns.primary) hosts domain (Dns.mkSOA "ns4.kitenet.net" 100) $ [ (RootDomain, NS $ AbsDomain "ns4.kitenet.net")@@ -594,20 +600,20 @@ monsters :: [Host] -- Systems I don't manage with propellor, monsters = -- but do want to track their public keys etc.- [ host "usw-s002.rsync.net"+ [ host "usw-s002.rsync.net" $ props & Ssh.hostPubKey SshEd25519 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB7yTEBGfQYdwG/oeL+U9XPMIh/dW7XNs9T+M79YIOrd"- , host "github.com"+ , host "github.com" $ props & Ssh.hostPubKey SshRsa "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ=="- , host "gitlab.com"+ , host "gitlab.com" $ props & Ssh.hostPubKey SshEcdsa "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY="- , host "ns6.gandi.net"+ , host "ns6.gandi.net" $ props & ipv4 "217.70.177.40"- , host "turtle.kitenet.net"+ , host "turtle.kitenet.net" $ props & ipv4 "67.223.19.96" & ipv6 "2001:4978:f:2d9::2"- , host "mouse.kitenet.net"+ , host "mouse.kitenet.net" $ props & ipv6 "2001:4830:1600:492::2"- , host "animx"+ , host "animx" $ props & ipv4 "76.7.162.101" & ipv4 "76.7.162.186" ]
propellor.cabal view
@@ -1,5 +1,5 @@ Name: propellor-Version: 2.17.2+Version: 3.0.0 Cabal-Version: >= 1.8 License: BSD3 Maintainer: Joey Hess <id@joeyh.name>@@ -20,6 +20,7 @@ joeyconfig.hs config.hs contrib/post-merge-hook+ stack.yaml debian/changelog debian/README.Debian debian/compat@@ -36,31 +37,39 @@ Executable propellor Main-Is: wrapper.hs- GHC-Options: -threaded -Wall -fno-warn-tabs+ GHC-Options: -threaded -Wall -fno-warn-tabs -O0+ Extensions: TypeOperators Hs-Source-Dirs: src- Build-Depends: + Build-Depends: -- propellor needs to support the ghc shipped in Debian stable base >= 4.5, base < 5, MissingH, directory, filepath, IfElse, process, bytestring, hslogger, unix, unix-compat, ansi-terminal, containers (>= 0.5), network, async,- time, mtl, transformers, exceptions (>= 0.6), stm, text+ time, mtl, transformers, exceptions (>= 0.6), stm, text,+ concurrent-output Executable propellor-config Main-Is: config.hs- GHC-Options: -threaded -Wall -fno-warn-tabs+ GHC-Options: -threaded -Wall -fno-warn-tabs -O0+ Extensions: TypeOperators Hs-Source-Dirs: src- Build-Depends: MissingH, directory, filepath, base >= 4.5, base < 5,- IfElse, process, bytestring, hslogger, unix-compat, ansi-terminal,- containers (>= 0.5), network, async, time, mtl, transformers,- exceptions (>= 0.6), stm, text, unix+ Build-Depends: + base >= 4.5, base < 5,+ MissingH, directory, filepath, IfElse, process, bytestring, hslogger,+ unix, unix-compat, ansi-terminal, containers (>= 0.5), network, async,+ time, mtl, transformers, exceptions (>= 0.6), stm, text,+ concurrent-output Library- GHC-Options: -Wall -fno-warn-tabs+ GHC-Options: -Wall -fno-warn-tabs -O0+ Extensions: TypeOperators Hs-Source-Dirs: src- Build-Depends: MissingH, directory, filepath, base >= 4.5, base < 5,- IfElse, process, bytestring, hslogger, unix-compat, ansi-terminal,- containers (>= 0.5), network, async, time, mtl, transformers,- exceptions (>= 0.6), stm, text, unix+ Build-Depends: + base >= 4.5, base < 5,+ MissingH, directory, filepath, IfElse, process, bytestring, hslogger,+ unix, unix-compat, ansi-terminal, containers (>= 0.5), network, async,+ time, mtl, transformers, exceptions (>= 0.6), stm, text,+ concurrent-output Exposed-Modules: Propellor@@ -70,6 +79,7 @@ Propellor.Property.Aiccu Propellor.Property.Apache Propellor.Property.Apt+ Propellor.Property.Apt.PPA Propellor.Property.Cmd Propellor.Property.Concurrent Propellor.Property.Conductor@@ -138,24 +148,30 @@ Propellor.PropAccum Propellor.Utilities Propellor.CmdLine+ Propellor.Container Propellor.Info Propellor.Message Propellor.Debug+ Propellor.DotDir Propellor.PrivData Propellor.Engine+ Propellor.EnsureProperty Propellor.Exception Propellor.Types+ Propellor.Types.Core Propellor.Types.Chroot+ Propellor.Types.CmdLine Propellor.Types.Container Propellor.Types.Docker Propellor.Types.Dns Propellor.Types.Empty Propellor.Types.Info+ Propellor.Types.MetaTypes Propellor.Types.OS Propellor.Types.PrivData Propellor.Types.Result Propellor.Types.ResultCheck- Propellor.Types.CmdLine+ Propellor.Types.Singletons Propellor.Types.ZFS Other-Modules: Propellor.Bootstrap@@ -193,9 +209,6 @@ Utility.ThreadScheduler Utility.Tmp Utility.UserInfo- System.Console.Concurrent- System.Console.Concurrent.Internal- System.Process.Concurrent source-repository head type: git
src/Propellor.hs view
@@ -14,13 +14,14 @@ -- > main = defaultMain hosts -- > -- > hosts :: [Host]--- > hosts =--- > [ host "example.com"+-- > hosts = [example]+-- > +-- > example :: Host+-- > example = host "example.com" $ props -- > & Apt.installed ["mydaemon"] -- > & "/etc/mydaemon.conf" `File.containsLine` "secure=1" -- > `onChange` cmdProperty "service" ["mydaemon", "restart"] -- > ! Apt.installed ["unwantedpackage"]--- > ] -- -- See config.hs for a more complete example, and clone Propellor's -- git repository for a deployable system using Propellor:@@ -38,7 +39,6 @@ , (&) , (!) -- * Propertries- , describe -- | Properties are often combined together in your propellor -- configuration. For example: --@@ -47,6 +47,7 @@ , requires , before , onChange+ , describe , module Propellor.Property -- | Everything you need to build your own properties, -- and useful property combinators
src/Propellor/Bootstrap.hs view
@@ -7,6 +7,7 @@ import Propellor.Base import Propellor.Types.Info+import Propellor.Git.Config import System.Posix.Files import Data.List@@ -71,7 +72,7 @@ , "cabal install --only-dependencies" ] - aptinstall p = "DEBIAN_FRONTEND=noninteractive apt-get --no-upgrade --no-install-recommends -y install " ++ p+ aptinstall p = "DEBIAN_FRONTEND=noninteractive apt-get -qq --no-upgrade --no-install-recommends -y install " ++ p pkginstall p = "ASSUME_ALWAYS_YES=yes pkg install " ++ p -- This is the same deps listed in debian/control.@@ -91,6 +92,7 @@ , "libghc-exceptions-dev" , "libghc-stm-dev" , "libghc-text-dev"+ , "libghc-concurrent-output-dev" , "make" ] fbsddeps =@@ -109,6 +111,7 @@ , "hs-exceptions" , "hs-stm" , "hs-text"+ , "hs-concurrent-output" , "gmake" ] @@ -126,27 +129,33 @@ use cmds = "if ! git --version >/dev/null; then " ++ intercalate " && " cmds ++ "; fi" apt = [ "apt-get update"- , "DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends --no-upgrade -y install git"+ , "DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends --no-upgrade -y install git" ] buildPropellor :: Maybe Host -> IO () buildPropellor mh = unlessM (actionMessage "Propellor build" (build msys)) $ errorMessage "Propellor build failed!" where- msys = case fmap (getInfo . hostInfo) mh of+ msys = case fmap (fromInfo . hostInfo) mh of Just (InfoVal sys) -> Just sys _ -> Nothing --- Build propellor using cabal, and symlink propellor to where cabal--- leaves the built binary.---+-- Build propellor using cabal or stack, and symlink propellor to the+-- built binary.+build :: Maybe System -> IO Bool+build msys = catchBoolIO $ do+ bs <- getGitConfigValue "propellor.buildsystem"+ case bs of+ Just "stack" -> stackBuild msys+ _ -> cabalBuild msys+ -- For speed, only runs cabal configure when it's not been run before. -- If the build fails cabal may need to have configure re-run. -- -- If the cabal configure fails, and a System is provided, installs -- dependencies and retries.-build :: Maybe System -> IO Bool-build msys = catchBoolIO $ do+cabalBuild :: Maybe System -> IO Bool+cabalBuild msys = do make "dist/setup-config" ["propellor.cabal"] cabal_configure unlessM cabal_build $ unlessM (cabal_configure <&&> cabal_build) $@@ -161,14 +170,11 @@ unlessM (boolSystem "cp" [Param "-af", Param cabalbuiltbin, Param (tmpfor safetycopy)]) $ error "cp of binary failed" rename (tmpfor safetycopy) safetycopy- createSymbolicLink safetycopy (tmpfor dest)- rename (tmpfor dest) dest+ symlinkPropellorBin safetycopy return True where- dest = "propellor" cabalbuiltbin = "dist/build/propellor-config/propellor-config" safetycopy = cabalbuiltbin ++ ".built"- tmpfor f = f ++ ".propellortmp" cabal_configure = ifM (cabal ["configure"]) ( return True , case msys of@@ -179,6 +185,35 @@ ) cabal_build = cabal ["build", "propellor-config"] +stackBuild :: Maybe System -> IO Bool+stackBuild _msys = do+ createDirectoryIfMissing True builddest+ ifM (stack buildparams)+ ( do+ symlinkPropellorBin (builddest </> "propellor-config")+ return True+ , return False+ )+ where+ builddest = ".built"+ buildparams =+ [ "--local-bin-path", builddest+ , "build"+ , ":propellor-config" -- only build config program+ , "--copy-bins"+ ]++-- Atomic symlink creation/update.+symlinkPropellorBin :: FilePath -> IO ()+symlinkPropellorBin bin = do+ createSymbolicLink bin (tmpfor dest)+ rename (tmpfor dest) dest+ where+ dest = "propellor"++tmpfor :: FilePath -> FilePath+tmpfor f = f ++ ".propellortmp"+ make :: FilePath -> [FilePath] -> IO Bool -> IO () make dest srcs builder = do dt <- getmtime dest@@ -191,3 +226,6 @@ cabal :: [String] -> IO Bool cabal = boolSystem "cabal" . map Param++stack :: [String] -> IO Bool+stack = boolSystem "stack" . map Param
src/Propellor/CmdLine.hs view
@@ -16,6 +16,7 @@ import Propellor.Bootstrap import Propellor.Spin import Propellor.Types.CmdLine+import Propellor.DotDir (interactiveInit) import qualified Propellor.Property.Docker as Docker import qualified Propellor.Property.Chroot as Chroot import qualified Propellor.Shim as Shim@@ -23,6 +24,7 @@ usage :: Handle -> IO () usage h = hPutStrLn h $ unlines [ "Usage:"+ , " propellor --init" , " propellor" , " propellor hostname" , " propellor --spin targethost [--via relayhost]"@@ -69,6 +71,7 @@ go ("--serialized":s:[]) = serialized Serialized s go ("--continue":s:[]) = serialized Continue s go ("--gitpush":fin:fout:_) = return $ GitPush (Prelude.read fin) (Prelude.read fout)+ go ("--init":_) = return Init go ("--run":h:[]) = go [h] go (h:[]) | "--" `isPrefixOf` h = usageError [h]@@ -130,6 +133,7 @@ fetchFirst (buildFirst (findHost hostlist hn) cr cmdline (runhost hn)) -- When continuing after a rebuild, don't want to rebuild again. go _ (Continue cmdline) = go NoRebuild cmdline+ go _ Init = interactiveInit withhost :: HostName -> (Host -> IO ()) -> IO () withhost hn a = maybe (unknownhost hn hostlist) a (findHost hostlist hn)@@ -149,6 +153,9 @@ -- Builds propellor (when allowed) and if it looks like a new binary, -- re-execs it to continue. -- Otherwise, runs the IO action to continue.+--+-- The Host should only be provided when dependencies should be installed+-- as needed to build propellor. buildFirst :: Maybe Host -> CanRebuild -> CmdLine -> IO () -> IO () buildFirst h CanRebuild cmdline next = do oldtime <- getmtime
+ src/Propellor/Container.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE DataKinds, TypeFamilies #-}++module Propellor.Container where++import Propellor.Types+import Propellor.Types.Core+import Propellor.Types.MetaTypes+import Propellor.Types.Info+import Propellor.Info+import Propellor.PrivData+import Propellor.PropAccum++class IsContainer c where+ containerProperties :: c -> [ChildProperty]+ containerInfo :: c -> Info+ setContainerProperties :: c -> [ChildProperty] -> c++instance IsContainer Host where+ containerProperties = hostProperties+ containerInfo = hostInfo+ setContainerProperties h ps = host (hostName h) (Props ps)++-- | Note that the metatype of a container's properties is not retained,+-- so this defaults to UnixLike. So, using this with setContainerProps can+-- add properties to a container that conflict with properties already in it.+-- Use caution when using this; only add properties that do not have+-- restricted targets.+containerProps :: IsContainer c => c -> Props UnixLike+containerProps = Props . containerProperties++setContainerProps :: IsContainer c => c -> Props metatypes -> c+setContainerProps c (Props ps) = setContainerProperties c ps++-- | Adjust the provided Property, adding to its+-- propertyChidren the properties of the provided container.+-- +-- The Info of the propertyChildren is adjusted to only include +-- info that should be propagated out to the Property.+--+-- Any PrivInfo that uses HostContext is adjusted to use the name+-- of the container as its context.+propagateContainer+ ::+ -- Since the children being added probably have info,+ -- require the Property's metatypes to have info.+ ( IncludesInfo metatypes ~ 'True+ , IsContainer c+ )+ => String+ -> c+ -> Property metatypes+ -> Property metatypes+propagateContainer containername c prop = prop+ `addChildren` map convert (containerProperties c)+ where+ convert p = + let n = property (getDesc p) (getSatisfy p) :: Property UnixLike+ n' = n+ `setInfoProperty` mapInfo (forceHostContext containername)+ (propagatableInfo (getInfo p))+ `addChildren` map convert (getChildren p)+ in toChildProperty n'
+ src/Propellor/DotDir.hs view
@@ -0,0 +1,393 @@+module Propellor.DotDir where++import Propellor.Message+import Propellor.Bootstrap+import Propellor.Git+import Propellor.Gpg+import Propellor.Types.Result+import Utility.UserInfo+import Utility.Monad+import Utility.Process+import Utility.SafeCommand+import Utility.Exception+import Utility.Path+-- This module is autogenerated by the build system.+import qualified Paths_propellor as Package++import Data.Char+import Data.List+import Data.Version+import Control.Monad+import Control.Monad.IfElse+import System.Directory+import System.FilePath+import System.Posix.Directory+import System.IO+import System.Console.Concurrent+import Control.Applicative+import Prelude++distdir :: FilePath+distdir = "/usr/src/propellor"++-- A distribution may include a bundle of propellor's git repository here.+-- If not, it will be pulled from the network when needed.+distrepo :: FilePath+distrepo = distdir </> "propellor.git"++-- File containing the head rev of the distrepo.+disthead :: FilePath+disthead = distdir </> "head"++upstreambranch :: String+upstreambranch = "upstream/master"++-- Using the github mirror of the main propellor repo because+-- it is accessible over https for better security.+netrepo :: String+netrepo = "https://github.com/joeyh/propellor.git"++dotPropellor :: IO FilePath+dotPropellor = do+ home <- myHomeDir+ return (home </> ".propellor")++-- Detect if propellor was built using stack. This is somewhat of a hack.+buildSystem :: IO String+buildSystem = do+ d <- Package.getLibDir+ return $ if "stack-work" `isInfixOf` d then "stack" else "cabal"++interactiveInit :: IO ()+interactiveInit = ifM (doesDirectoryExist =<< dotPropellor)+ ( error "~/.propellor/ already exists, not doing anything"+ , do+ welcomeBanner+ setup+ )++say :: String -> IO ()+say = outputConcurrent++sayLn :: String -> IO ()+sayLn s = say (s ++ "\n")++welcomeBanner :: IO ()+welcomeBanner = say $ unlines $ map prettify+ [ ""+ , ""+ , " _ ______`| ,-.__"+ , " .--------------------------- / ~___-=O`/|O`/__| (____.'"+ , " - Welcome to -- ~ / | / ) _.-'-._"+ , " - Propellor! -- `/-==__ _/__|/__=-| ( ~_"+ , " `--------------------------- * ~ | | '--------'"+ , " (o) `"+ , ""+ , ""+ ]+ where+ prettify = map (replace '~' '\\')+ replace x y c+ | c == x = y+ | otherwise = c++prompt :: String -> [(String, IO ())] -> IO ()+prompt p cs = do+ say (p ++ " [" ++ intercalate "|" (map fst cs) ++ "] ")+ flushConcurrentOutput+ hFlush stdout+ r <- map toLower <$> getLine+ if null r+ then snd (head cs) -- default to first choice on return+ else case filter (\(s, _) -> map toLower s == r) cs of+ [(_, a)] -> a+ _ -> do+ sayLn "Not a valid choice, try again.. (Or ctrl-c to quit)"+ prompt p cs++section :: IO ()+section = do+ sayLn ""+ sayLn "------------------------------------------------------------------------------"+ sayLn ""++setup :: IO ()+setup = do+ sayLn "Propellor's configuration file is ~/.propellor/config.hs"+ sayLn ""+ sayLn "Lets get you started with a simple config that you can adapt"+ sayLn "to your needs. You can start with:"+ sayLn " A: A clone of propellor's git repository (most flexible)"+ sayLn " B: The bare minimum files to use propellor (most simple)"+ prompt "Which would you prefer?"+ [ ("A", void $ actionMessage "Cloning propellor's git repository" fullClone)+ , ("B", void $ actionMessage "Creating minimal config" minimalConfig)+ ]+ changeWorkingDirectory =<< dotPropellor++ section+ sayLn "Let's try building the propellor configuration, to make sure it will work..."+ sayLn ""+ b <- buildSystem+ void $ boolSystem "git"+ [ Param "config"+ , Param "propellor.buildsystem"+ , Param b+ ]+ buildPropellor Nothing+ sayLn ""+ sayLn "Great! Propellor is bootstrapped."+ + section+ sayLn "Propellor can use gpg to encrypt private data about the systems it manages,"+ sayLn "and to sign git commits."+ gpg <- getGpgBin+ ifM (inPath gpg)+ ( setupGpgKey+ , do+ sayLn "You don't seem to have gpg installed, so skipping setting it up."+ explainManualSetupGpgKey+ )++ section+ sayLn "Everything is set up ..."+ sayLn "Your next step is to edit ~/.propellor/config.hs"+ sayLn "and run propellor again to try it out."+ sayLn ""+ sayLn "For docs, see https://propellor.branchable.com/"+ sayLn "Enjoy propellor!"++explainManualSetupGpgKey :: IO ()+explainManualSetupGpgKey = do+ sayLn "Propellor can still be used without gpg, but it won't be able to"+ sayLn "manage private data. You can set this up later:"+ sayLn " 1. gpg --gen-key"+ sayLn " 2. propellor --add-key (pass it the key ID generated in step 1)"++setupGpgKey :: IO ()+setupGpgKey = do+ ks <- listSecretKeys+ sayLn ""+ case ks of+ [] -> makeGpgKey+ [(k, d)] -> do+ sayLn $ "You have one gpg key: " ++ desckey k d+ prompt "Should propellor use that key?"+ [ ("Y", propellorAddKey k)+ , ("N", sayLn $ "Skipping gpg setup. If you change your mind, run: propellor --add-key " ++ k)+ ]+ _ -> do+ let nks = zip ks (map show ([1..] :: [Integer]))+ sayLn "I see you have several gpg keys:"+ forM_ nks $ \((k, d), n) ->+ sayLn $ " " ++ n ++ " " ++ desckey k d+ prompt "Which of your gpg keys should propellor use?"+ (map (\((k, _), n) -> (n, propellorAddKey k)) nks)+ where+ desckey k d = d ++ " (keyid " ++ k ++ ")"++makeGpgKey :: IO ()+makeGpgKey = do+ sayLn "You seem to not have any gpg secret keys."+ prompt "Would you like to create one now?"+ [("Y", rungpg), ("N", nope)]+ where+ nope = do+ sayLn "No problem."+ explainManualSetupGpgKey+ rungpg = do+ sayLn "Running gpg --gen-key ..."+ gpg <- getGpgBin+ void $ boolSystem gpg [Param "--gen-key"]+ ks <- listSecretKeys+ case ks of+ [] -> do+ sayLn "Hmm, gpg seemed to not set up a secret key."+ prompt "Want to try running gpg again?"+ [("Y", rungpg), ("N", nope)]+ ((k, _):_) -> propellorAddKey k++propellorAddKey :: String -> IO ()+propellorAddKey keyid = do+ sayLn ""+ sayLn $ "Telling propellor to use your gpg key by running: propellor --add-key " ++ keyid+ d <- dotPropellor+ unlessM (boolSystem (d </> "propellor") [Param "--add-key", Param keyid]) $ do+ sayLn "Oops, that didn't work! You can retry the same command later."+ sayLn "Continuing onward ..."++minimalConfig :: IO Result+minimalConfig = do+ d <- dotPropellor+ createDirectoryIfMissing True d+ changeWorkingDirectory d+ void $ boolSystem "git" [Param "init"]+ addfile "config.cabal" cabalcontent+ addfile "config.hs" configcontent+ addfile "stack.yaml" stackcontent+ return MadeChange+ where+ addfile f content = do+ writeFile f (unlines content)+ void $ boolSystem "git" [Param "add" , File f]+ cabalcontent =+ [ "-- This is a cabal file to use to build your propellor configuration."+ , ""+ , "Name: config"+ , "Cabal-Version: >= 1.6"+ , "Build-Type: Simple"+ , "Version: 0"+ , ""+ , "Executable propellor-config"+ , " Main-Is: config.hs"+ , " GHC-Options: -threaded -Wall -fno-warn-tabs -O0"+ , " Extensions: TypeOperators"+ , " Build-Depends: propellor >= 3.0, base >= 3"+ ]+ configcontent = + [ "-- This is the main configuration file for Propellor, and is used to build"+ , "-- the propellor program. https://propellor.branchable.com/"+ , ""+ , "import Propellor"+ , "import qualified Propellor.Property.File as File"+ , "import qualified Propellor.Property.Apt as Apt"+ , "import qualified Propellor.Property.Cron as Cron"+ , "import qualified Propellor.Property.User as User"+ , ""+ , "main :: IO ()"+ , "main = defaultMain hosts"+ , ""+ , "-- The hosts propellor knows about."+ , "hosts :: [Host]"+ , "hosts ="+ , " [ mybox"+ , " ]"+ , ""+ , "-- An example host."+ , "mybox :: Host"+ , "mybox = host \"mybox.example.com\" $ props"+ , " & osDebian Unstable \"amd64\""+ , " & Apt.stdSourcesList"+ , " & Apt.unattendedUpgrades"+ , " & Apt.installed [\"etckeeper\"]"+ , " & Apt.installed [\"ssh\"]"+ , " & User.hasSomePassword (User \"root\")"+ , " & File.dirExists \"/var/www\""+ , " & Cron.runPropellor (Cron.Times \"30 * * * *\")"+ , ""+ ]+ stackcontent =+ -- This should be the same resolver version in propellor's+ -- own stack.yaml+ [ "resolver: lts-5.10"+ , "packages:"+ , "- '.'"+ , "extra-deps:"+ , "- propellor-" ++ showVersion Package.version+ ]++fullClone :: IO Result+fullClone = do+ d <- dotPropellor+ let enterdotpropellor = changeWorkingDirectory d >> return True+ ok <- ifM (doesFileExist distrepo <||> doesDirectoryExist distrepo)+ ( allM id+ [ boolSystem "git" [Param "clone", File distrepo, File d]+ , fetchUpstreamBranch distrepo+ , enterdotpropellor+ , boolSystem "git" [Param "remote", Param "rm", Param "origin"]+ ]+ , allM id+ [ boolSystem "git" [Param "clone", Param netrepo, File d]+ , enterdotpropellor+ -- Rename origin to upstream and avoid+ -- git push to that read-only repo.+ , boolSystem "git" [Param "remote", Param "rename", Param "origin", Param "upstream"]+ , boolSystem "git" [Param "config", Param "--unset", Param "branch.master.remote", Param "upstream"]+ ]+ )+ return (toResult ok)++fetchUpstreamBranch :: FilePath -> IO Bool+fetchUpstreamBranch repo = do+ changeWorkingDirectory =<< dotPropellor+ boolSystem "git"+ [ Param "fetch"+ , File repo+ , Param ("+refs/heads/master:refs/remotes/" ++ upstreambranch)+ , Param "--quiet"+ ]++checkRepoUpToDate :: IO ()+checkRepoUpToDate = whenM (gitbundleavail <&&> dotpropellorpopulated) $ do+ headrev <- takeWhile (/= '\n') <$> readFile disthead+ changeWorkingDirectory =<< dotPropellor+ headknown <- catchMaybeIO $ + withQuietOutput createProcessSuccess $+ proc "git" ["log", headrev]+ if (headknown == Nothing)+ then setupUpstreamMaster headrev+ else do+ theirhead <- getCurrentGitSha1 =<< getCurrentBranchRef+ when (theirhead /= headrev) $ do+ merged <- not . null <$>+ readProcess "git" ["log", headrev ++ "..HEAD", "--ancestry-path"]+ unless merged $+ warnoutofdate True+ where+ gitbundleavail = doesFileExist disthead+ dotpropellorpopulated = do+ d <- dotPropellor+ doesFileExist (d </> "propellor.cabal")++-- Makes upstream/master in dotPropellor be a usefully mergeable branch.+--+-- We cannot just use origin/master, because in the case of a distrepo,+-- it only contains 1 commit. So, trying to merge with it will result+-- in lots of merge conflicts, since git cannot find a common parent+-- commit.+--+-- Instead, the upstream/master branch is created by taking the+-- upstream/master branch (which must be an old version of propellor,+-- as distributed), and diffing from it to the current origin/master,+-- and committing the result. This is done in a temporary clone of the+-- repository, giving it a new master branch. That new branch is fetched+-- into the user's repository, as if fetching from a upstream remote,+-- yielding a new upstream/master branch.+setupUpstreamMaster :: String -> IO ()+setupUpstreamMaster newref = do+ changeWorkingDirectory =<< dotPropellor+ go =<< catchMaybeIO getoldrev+ where+ go Nothing = warnoutofdate False+ go (Just oldref) = do+ let tmprepo = ".git/propellordisttmp"+ let cleantmprepo = void $ catchMaybeIO $ removeDirectoryRecursive tmprepo+ cleantmprepo+ git ["clone", "--quiet", ".", tmprepo]+ + changeWorkingDirectory tmprepo+ git ["fetch", distrepo, "--quiet"]+ git ["reset", "--hard", oldref, "--quiet"]+ git ["merge", newref, "-s", "recursive", "-Xtheirs", "--quiet", "-m", "merging upstream version"]+ + void $ fetchUpstreamBranch tmprepo+ cleantmprepo+ warnoutofdate True++ getoldrev = takeWhile (/= '\n')+ <$> readProcess "git" ["show-ref", upstreambranch, "--hash"]+ + git = run "git"+ run cmd ps = unlessM (boolSystem cmd (map Param ps)) $+ error $ "Failed to run " ++ cmd ++ " " ++ show ps++warnoutofdate :: Bool -> IO ()+warnoutofdate havebranch = do+ warningMessage ("** Your ~/.propellor/ is out of date..")+ let also s = hPutStrLn stderr (" " ++ s)+ also ("A newer upstream version is available in " ++ distrepo)+ if havebranch+ then also ("To merge it, run: git merge " ++ upstreambranch)+ else also ("To merge it, find the most recent commit in your repository's history that corresponds to an upstream release of propellor, and set refs/remotes/" ++ upstreambranch ++ " to it. Then run propellor again.")+ also ""
src/Propellor/Engine.hs view
@@ -1,11 +1,10 @@ {-# LANGUAGE PackageImports #-}-{-# LANGUAGE GADTs #-}+{-# LANGUAGE DataKinds #-} module Propellor.Engine ( mainProperties, runPropellor,- ensureProperty,- ensureProperties,+ ensureChildProperties, fromHost, fromHost', onlyProcess,@@ -23,24 +22,26 @@ import Prelude import Propellor.Types+import Propellor.Types.MetaTypes+import Propellor.Types.Core import Propellor.Message import Propellor.Exception import Propellor.Info-import Propellor.Property import Utility.Exception -- | Gets the Properties of a Host, and ensures them all, -- with nice display of what's being done. mainProperties :: Host -> IO () mainProperties host = do- ret <- runPropellor host $- ensureProperties [ignoreInfo $ infoProperty "overall" (ensureProperties ps) mempty mempty]+ ret <- runPropellor host $ ensureChildProperties [toChildProperty overall] messagesDone case ret of FailedChange -> exitWith (ExitFailure 1) _ -> exitWith ExitSuccess where- ps = map ignoreInfo $ hostProperties host+ overall :: Property (MetaTypes '[])+ overall = property "overall" $+ ensureChildProperties (hostProperties host) -- | Runs a Propellor action with the specified host. --@@ -58,14 +59,14 @@ (ret, _s, _) <- runRWST (runWithHost (catchPropellor (a res))) host () return ret --- | Ensures a list of Properties, with a display of each as it runs.-ensureProperties :: [Property NoInfo] -> Propellor Result-ensureProperties ps = ensure ps NoChange+-- | Ensures the child properties, with a display of each as it runs.+ensureChildProperties :: [ChildProperty] -> Propellor Result+ensureChildProperties ps = ensure ps NoChange where ensure [] rs = return rs ensure (p:ls) rs = do hn <- asks hostName- r <- actionMessageOn hn (propertyDesc p) (ensureProperty p)+ r <- actionMessageOn hn (getDesc p) (catchPropellor $ getSatisfy p) ensure ls (r <> rs) -- | Lifts an action into the context of a different host.
+ src/Propellor/EnsureProperty.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++module Propellor.EnsureProperty+ ( ensureProperty+ , property'+ , OuterMetaTypesWitness(..)+ ) where++import Propellor.Types+import Propellor.Types.Core+import Propellor.Types.MetaTypes+import Propellor.Exception++import Data.Monoid+import Prelude++-- | For when code running in the Propellor monad needs to ensure a+-- Property.+--+-- Use `property'` to get the `OuterMetaTypesWithness`. For example:+--+-- > foo = Property Debian+-- > foo = property' $ \w -> do+-- > ensureProperty w (aptInstall "foo")+--+-- The type checker will prevent using ensureProperty with a property+-- that does not support the target OSes needed by the OuterMetaTypesWitness.+-- In the example above, aptInstall must support Debian, since foo+-- is supposed to support Debian.+--+-- The type checker will also prevent using ensureProperty with a property+-- with HasInfo in its MetaTypes. Doing so would cause the `Info` associated+-- with the property to be lost.+ensureProperty+ ::+ ( Cannot_ensureProperty_WithInfo inner ~ 'True+ , (Targets inner `NotSuperset` Targets outer) ~ 'CanCombine+ )+ => OuterMetaTypesWitness outer+ -> Property (MetaTypes inner)+ -> Propellor Result+ensureProperty _ = catchPropellor . getSatisfy++-- The name of this was chosen to make type errors a more understandable.+type family Cannot_ensureProperty_WithInfo (l :: [a]) :: Bool+type instance Cannot_ensureProperty_WithInfo '[] = 'True+type instance Cannot_ensureProperty_WithInfo (t ': ts) =+ Not (t `EqT` 'WithInfo) && Cannot_ensureProperty_WithInfo ts++-- | Constructs a property, like `property`, but provides its+-- `OuterMetaTypesWitness`.+property'+ :: SingI metatypes+ => Desc+ -> (OuterMetaTypesWitness metatypes -> Propellor Result)+ -> Property (MetaTypes metatypes)+property' d a =+ let p = Property sing d (a (outerMetaTypesWitness p)) mempty mempty+ in p++-- | Used to provide the metatypes of a Property to calls to +-- 'ensureProperty` within it.+newtype OuterMetaTypesWitness metatypes = OuterMetaTypesWitness (MetaTypes metatypes)++outerMetaTypesWitness :: Property (MetaTypes l) -> OuterMetaTypesWitness l+outerMetaTypesWitness (Property metatypes _ _ _ _) = OuterMetaTypesWitness metatypes
src/Propellor/Gpg.hs view
@@ -32,14 +32,21 @@ -- Lists the keys in propellor's keyring. listPubKeys :: IO [KeyId] listPubKeys = do- gpgbin <- getGpgBin keyring <- privDataKeyring- parse . lines <$> readProcess gpgbin (listopts keyring)+ map fst <$> listKeys ("--list-public-keys" : useKeyringOpts keyring)++listSecretKeys :: IO [(KeyId, String)]+listSecretKeys = listKeys ["--list-secret-keys"]++listKeys :: [String] -> IO [(KeyId, String)]+listKeys ps = do+ gpgbin <- getGpgBin+ parse . lines <$> readProcess gpgbin listopts where- listopts keyring = useKeyringOpts keyring ++- ["--with-colons", "--list-public-keys"]+ listopts = ps ++ ["--with-colons"] parse = mapMaybe (keyIdField . split ":")- keyIdField ("pub":_:_:_:f:_) = Just f+ keyIdField (t:_:_:_:f:_:_:_:_:n:_)+ | t == "pub" || t == "sec" = Just (f, n) keyIdField _ = Nothing useKeyringOpts :: FilePath -> [String]
src/Propellor/Info.hs view
@@ -1,9 +1,30 @@-{-# LANGUAGE PackageImports #-}+{-# LANGUAGE PackageImports, TypeFamilies, DataKinds, PolyKinds #-} -module Propellor.Info where+module Propellor.Info (+ osDebian,+ osBuntish,+ osFreeBSD,+ setInfoProperty,+ addInfoProperty,+ pureInfoProperty,+ pureInfoProperty',+ askInfo,+ getOS,+ ipv4,+ ipv6,+ alias,+ addDNS,+ hostMap,+ aliasMap,+ findHost,+ findHostNoAlias,+ getAddresses,+ hostAddresses,+) where import Propellor.Types import Propellor.Types.Info+import Propellor.Types.MetaTypes import "mtl" Control.Monad.Reader import qualified Data.Set as S@@ -13,21 +34,67 @@ import Control.Applicative import Prelude -pureInfoProperty :: (IsInfo v) => Desc -> v -> Property HasInfo-pureInfoProperty desc v = pureInfoProperty' desc (addInfo mempty v)+-- | Adds info to a Property.+--+-- The new Property will include HasInfo in its metatypes.+setInfoProperty+ :: (MetaTypes metatypes' ~ (+) HasInfo metatypes, SingI metatypes')+ => Property metatypes+ -> Info+ -> Property (MetaTypes metatypes')+setInfoProperty (Property _ d a oldi c) newi =+ Property sing d a (oldi <> newi) c -pureInfoProperty' :: Desc -> Info -> Property HasInfo-pureInfoProperty' desc i = infoProperty ("has " ++ desc) (return NoChange) i mempty+-- | Adds more info to a Property that already HasInfo.+addInfoProperty+ :: (IncludesInfo metatypes ~ 'True)+ => Property metatypes+ -> Info+ -> Property metatypes+addInfoProperty (Property t d a oldi c) newi =+ Property t d a (oldi <> newi) c +-- | Makes a property that does nothing but set some `Info`.+pureInfoProperty :: (IsInfo v) => Desc -> v -> Property (HasInfo + UnixLike)+pureInfoProperty desc v = pureInfoProperty' desc (toInfo v)++pureInfoProperty' :: Desc -> Info -> Property (HasInfo + UnixLike)+pureInfoProperty' desc i = setInfoProperty p i+ where+ p :: Property UnixLike+ p = property ("has " ++ desc) (return NoChange)+ -- | Gets a value from the host's Info. askInfo :: (IsInfo v) => Propellor v-askInfo = asks (getInfo . hostInfo)+askInfo = asks (fromInfo . hostInfo) --- | Specifies the operating system of a host.+-- | Specifies that a host's operating system is Debian,+-- and further indicates the suite and architecture.+-- +-- This provides info for other Properties, so they can act+-- conditionally on the details of the OS. ----- This only provides info for other Properties, so they can act--- conditionally on the os.-os :: System -> Property HasInfo+-- It also lets the type checker know that all the properties of the+-- host must support Debian.+--+-- > & osDebian (Stable "jessie") "amd64"+osDebian :: DebianSuite -> Architecture -> Property (HasInfo + Debian)+osDebian suite arch = tightenTargets $ os (System (Debian suite) arch)++-- | Specifies that a host's operating system is a well-known Debian+-- derivative founded by a space tourist.+--+-- (The actual name of this distribution is not used in Propellor per+-- <http://joeyh.name/blog/entry/trademark_nonsense/>)+osBuntish :: Release -> Architecture -> Property (HasInfo + Buntish)+osBuntish release arch = tightenTargets $ os (System (Buntish release) arch)++-- | Specifies that a host's operating system is FreeBSD+-- and further indicates the release and architecture.+osFreeBSD :: FreeBSDRelease -> Architecture -> Property (HasInfo + FreeBSD)+osFreeBSD release arch = tightenTargets $ os (System (FreeBSD release) arch)++os :: System -> Property (HasInfo + UnixLike) os system = pureInfoProperty ("Operating " ++ show system) (InfoVal system) -- Gets the operating system of a host, if it has been specified.@@ -43,11 +110,11 @@ -- When propellor --spin is used to deploy a host, it checks -- if the host's IP Property matches the DNS. If the DNS is missing or -- out of date, the host will instead be contacted directly by IP address.-ipv4 :: String -> Property HasInfo+ipv4 :: String -> Property (HasInfo + UnixLike) ipv4 = addDNS . Address . IPv4 -- | Indicate that a host has an AAAA record in the DNS.-ipv6 :: String -> Property HasInfo+ipv6 :: String -> Property (HasInfo + UnixLike) ipv6 = addDNS . Address . IPv6 -- | Indicates another name for the host in the DNS.@@ -56,14 +123,14 @@ -- to use their address, rather than using a CNAME. This avoids various -- problems with CNAMEs, and also means that when multiple hosts have the -- same alias, a DNS round-robin is automatically set up.-alias :: Domain -> Property HasInfo+alias :: Domain -> Property (HasInfo + UnixLike) alias d = pureInfoProperty' ("alias " ++ d) $ mempty `addInfo` toAliasesInfo [d] -- A CNAME is added here, but the DNS setup code converts it to an -- IP address when that makes sense. `addInfo` (toDnsInfo $ S.singleton $ CNAME $ AbsDomain d) -addDNS :: Record -> Property HasInfo+addDNS :: Record -> Property (HasInfo + UnixLike) addDNS r = pureInfoProperty (rdesc r) (toDnsInfo (S.singleton r)) where rdesc (CNAME d) = unwords ["alias", ddesc d]@@ -86,7 +153,7 @@ aliasMap :: [Host] -> M.Map HostName Host aliasMap = M.fromList . concat .- map (\h -> map (\aka -> (aka, h)) $ fromAliasesInfo $ getInfo $ hostInfo h)+ map (\h -> map (\aka -> (aka, h)) $ fromAliasesInfo $ fromInfo $ hostInfo h) findHost :: [Host] -> HostName -> Maybe Host findHost l hn = (findHostNoAlias l hn) <|> (findAlias l hn)@@ -98,10 +165,7 @@ findAlias l hn = M.lookup hn (aliasMap l) getAddresses :: Info -> [IPAddr]-getAddresses = mapMaybe getIPAddr . S.toList . fromDnsInfo . getInfo+getAddresses = mapMaybe getIPAddr . S.toList . fromDnsInfo . fromInfo hostAddresses :: HostName -> [Host] -> [IPAddr] hostAddresses hn hosts = maybe [] (getAddresses . hostInfo) (findHost hosts hn)--addHostInfo ::IsInfo v => Host -> v -> Host-addHostInfo h v = h { hostInfo = addInfo (hostInfo h) v }
src/Propellor/PrivData.hs view
@@ -2,6 +2,8 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE DataKinds #-} module Propellor.PrivData ( withPrivData,@@ -40,6 +42,7 @@ import Propellor.Types import Propellor.Types.PrivData+import Propellor.Types.MetaTypes import Propellor.Types.Info import Propellor.Message import Propellor.Info@@ -75,29 +78,41 @@ -- being used, which is necessary to ensure that the privdata is sent to -- the remote host by propellor. withPrivData- :: (IsContext c, IsPrivDataSource s, IsProp (Property i))+ ::+ ( IsContext c+ , IsPrivDataSource s+ , IncludesInfo metatypes ~ 'True+ ) => s -> c- -> (((PrivData -> Propellor Result) -> Propellor Result) -> Property i)- -> Property HasInfo+ -> (((PrivData -> Propellor Result) -> Propellor Result) -> Property metatypes)+ -> Property metatypes withPrivData s = withPrivData' snd [s] -- Like withPrivData, but here any one of a list of PrivDataFields can be used. withSomePrivData- :: (IsContext c, IsPrivDataSource s, IsProp (Property i))+ ::+ ( IsContext c+ , IsPrivDataSource s+ , IncludesInfo metatypes ~ 'True+ ) => [s] -> c- -> ((((PrivDataField, PrivData) -> Propellor Result) -> Propellor Result) -> Property i)- -> Property HasInfo+ -> ((((PrivDataField, PrivData) -> Propellor Result) -> Propellor Result) -> Property metatypes)+ -> Property metatypes withSomePrivData = withPrivData' id withPrivData' - :: (IsContext c, IsPrivDataSource s, IsProp (Property i))+ ::+ ( IsContext c+ , IsPrivDataSource s+ , IncludesInfo metatypes ~ 'True+ ) => ((PrivDataField, PrivData) -> v) -> [s] -> c- -> (((v -> Propellor Result) -> Propellor Result) -> Property i)- -> Property HasInfo+ -> (((v -> Propellor Result) -> Propellor Result) -> Property metatypes)+ -> Property metatypes withPrivData' feed srclist c mkprop = addinfo $ mkprop $ \a -> maybe missing (a . feed) =<< getM get fieldlist where@@ -112,11 +127,7 @@ "Fix this by running:" : showSet (map (\s -> (privDataField s, Context cname, describePrivDataSource s)) srclist) return FailedChange- addinfo p = infoProperty- (propertyDesc p)- (propertySatisfy p)- (propertyInfo p `addInfo` privset)- (propertyChildren p)+ addinfo p = p `addInfoProperty` (toInfo privset) privset = PrivInfo $ S.fromList $ map (\s -> (privDataField s, describePrivDataSource s, hc)) srclist fieldnames = map show fieldlist@@ -132,7 +143,7 @@ , Just "" ] -addPrivData :: (PrivDataField, Maybe PrivDataSourceDesc, HostContext) -> Property HasInfo+addPrivData :: (PrivDataField, Maybe PrivDataSourceDesc, HostContext) -> Property (HasInfo + UnixLike) addPrivData v = pureInfoProperty (show v) (PrivInfo (S.singleton v)) {- Gets the requested field's value, in the specified context if it's@@ -150,7 +161,7 @@ filterPrivData host = M.filterWithKey (\k _v -> S.member k used) where used = S.map (\(f, _, c) -> (f, mkHostContext c (hostName host))) $- fromPrivInfo $ getInfo $ hostInfo host+ fromPrivInfo $ fromInfo $ hostInfo host getPrivData :: PrivDataField -> Context -> PrivMap -> Maybe PrivData getPrivData field context m = do@@ -234,7 +245,7 @@ mkPrivDataMap :: Host -> (Maybe PrivDataSourceDesc -> a) -> M.Map (PrivDataField, Context) a mkPrivDataMap host mkv = M.fromList $ map (\(f, d, c) -> ((f, mkHostContext c (hostName host)), mkv d))- (S.toList $ fromPrivInfo $ getInfo $ hostInfo host)+ (S.toList $ fromPrivInfo $ fromInfo $ hostInfo host) setPrivDataTo :: PrivDataField -> Context -> PrivData -> IO () setPrivDataTo field context (PrivData value) = do
src/Propellor/PropAccum.hs view
@@ -1,88 +1,86 @@-{-# LANGUAGE PackageImports, FlexibleContexts #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE DataKinds #-} module Propellor.PropAccum ( host- , PropAccum(..)+ , Props(..)+ , props , (&) , (&^) , (!)- , propagateContainer ) where -import Data.Monoid- import Propellor.Types+import Propellor.Types.MetaTypes+import Propellor.Types.Core import Propellor.Property-import Propellor.Types.Info-import Propellor.PrivData --- | Starts accumulating the properties of a Host.+import Data.Monoid+import Prelude++-- | Defines a host and its properties. ----- > host "example.com"+-- > host "example.com" $ props -- > & someproperty -- > ! oldproperty -- > & otherproperty-host :: HostName -> Host-host hn = Host hn [] mempty+host :: HostName -> Props metatypes -> Host+host hn (Props ps) = Host hn ps (mconcat (map getInfoRecursive ps)) --- | Something that can accumulate properties.-class PropAccum h where- -- | Adds a property.- addProp :: IsProp p => h -> p -> h+-- | Start accumulating a list of properties.+--+-- Properties can be added to it using `(&)` etc.+props :: Props UnixLike+props = Props [] - -- | Like addProp, but adds the property at the front of the list.- addPropFront :: IsProp p => h -> p -> h+infixl 1 &+infixl 1 &^+infixl 1 ! - getProperties :: h -> [Property HasInfo]+type family GetMetaTypes x+type instance GetMetaTypes (Property (MetaTypes t)) = MetaTypes t+type instance GetMetaTypes (RevertableProperty (MetaTypes t) undo) = MetaTypes t --- | Adds a property to a `Host` or other `PropAccum`+-- | Adds a property to a Props. -- -- Can add Properties and RevertableProperties-(&) :: (PropAccum h, IsProp p) => h -> p -> h-(&) = addProp+(&)+ ::+ ( IsProp p+ , MetaTypes y ~ GetMetaTypes p+ , CheckCombinable x y ~ 'CanCombine+ )+ => Props (MetaTypes x)+ -> p+ -> Props (MetaTypes (Combine x y))+Props c & p = Props (c ++ [toChildProperty p]) -- | Adds a property before any other properties.-(&^) :: (PropAccum h, IsProp p) => h -> p -> h-(&^) = addPropFront+(&^)+ ::+ ( IsProp p+ , MetaTypes y ~ GetMetaTypes p+ , CheckCombinable x y ~ 'CanCombine+ )+ => Props (MetaTypes x)+ -> p+ -> Props (MetaTypes (Combine x y))+Props c &^ p = Props (toChildProperty p : c) -- | Adds a property in reverted form.-(!) :: IsProp (RevertableProperty i) => PropAccum h => h -> RevertableProperty i -> h-h ! p = h & revert p--infixl 1 &-infixl 1 &^-infixl 1 !--instance PropAccum Host where- (Host hn ps is) `addProp` p = Host hn (ps ++ [toProp p])- (is <> getInfoRecursive p)- (Host hn ps is) `addPropFront` p = Host hn (toProp p : ps)- (getInfoRecursive p <> is)- getProperties = hostProperties+(!)+ :: (CheckCombinable x z ~ 'CanCombine)+ => Props (MetaTypes x)+ -> RevertableProperty (MetaTypes y) (MetaTypes z)+ -> Props (MetaTypes (Combine x z))+Props c ! p = Props (c ++ [toChildProperty (revert p)]) --- | Adjust the provided Property, adding to its--- propertyChidren the properties of the provided container.--- --- The Info of the propertyChildren is adjusted to only include --- info that should be propagated out to the Property.------ Any PrivInfo that uses HostContext is adjusted to use the name--- of the container as its context.-propagateContainer- :: (PropAccum container)- => String- -> container- -> Property HasInfo- -> Property HasInfo-propagateContainer containername c prop = infoProperty- (propertyDesc prop)- (propertySatisfy prop)- (propertyInfo prop)- (propertyChildren prop ++ hostprops)- where- hostprops = map go $ getProperties c- go p = - let i = mapInfo (forceHostContext containername)- (propagatableInfo (propertyInfo p))- cs = map go (propertyChildren p)- in infoProperty (propertyDesc p) (propertySatisfy p) i cs+-- addPropsHost :: Host -> [Prop] -> Host+-- addPropsHost (Host hn ps i) p = Host hn ps' i'+-- where+-- ps' = ps ++ [toChildProperty p]+-- i' = i <> getInfoRecursive p
src/Propellor/Property.hs view
@@ -1,5 +1,9 @@ {-# LANGUAGE PackageImports #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE PolyKinds #-} module Propellor.Property ( -- * Property combinators@@ -18,9 +22,13 @@ -- * Constructing properties , Propellor , property+ , property'+ , OuterMetaTypesWitness , ensureProperty+ , pickOS , withOS , unsupportedOS+ , unsupportedOS' , makeChange , noChange , doNothing@@ -44,22 +52,21 @@ import "mtl" Control.Monad.RWS.Strict import System.Posix.Files import qualified Data.Hash.MD5 as MD5+import Data.List import Control.Applicative import Prelude import Propellor.Types+import Propellor.Types.Core import Propellor.Types.ResultCheck+import Propellor.Types.MetaTypes+import Propellor.Types.Singletons import Propellor.Info-import Propellor.Exception+import Propellor.EnsureProperty import Utility.Exception import Utility.Monad import Utility.Misc --- | Constructs a Property, from a description and an action to run to--- ensure the Property is met.-property :: Desc -> Propellor Result -> Property NoInfo-property d s = simpleProperty d s mempty- -- | Makes a perhaps non-idempotent Property be idempotent by using a flag -- file to indicate whether it has run before. -- Use with caution.@@ -164,13 +171,6 @@ (==>) = flip describe infixl 1 ==> --- | For when code running in the Propellor monad needs to ensure a--- Property.------ This can only be used on a Property that has NoInfo.-ensureProperty :: Property NoInfo -> Propellor Result-ensureProperty = catchPropellor . propertySatisfy- -- | Tries the first property, but if it fails to work, instead uses -- the second. fallback :: (Combines p1 p2) => p1 -> p2 -> CombinedType p1 p2@@ -249,28 +249,96 @@ where mtime f = catchMaybeIO $ modificationTimeHiRes <$> getFileStatus f --- | Makes a property that is satisfied differently depending on the host's--- operating system. +-- | Picks one of the two input properties to use,+-- depending on the targeted OS. ----- Note that the operating system may not be declared for all hosts.+-- If both input properties support the targeted OS, then the+-- first will be used. ----- > myproperty = withOS "foo installed" $ \o -> case o of--- > (Just (System (Debian suite) arch)) -> ...--- > (Just (System (Buntish release) arch)) -> ...--- > Nothing -> unsupportedOS-withOS :: Desc -> (Maybe System -> Propellor Result) -> Property NoInfo-withOS desc a = property desc $ a =<< getOS+-- The resulting property will use the description of the first property+-- no matter which property is used in the end. So, it's often a good+-- idea to change the description to something clearer.+--+-- For example:+--+-- > upgraded :: UnixLike+-- > upgraded = (Apt.upgraded `pickOS` Pkg.upgraded)+-- > `describe` "OS upgraded"+--+-- If neither input property supports the targeted OS, calls+-- `unsupportedOS`. Using the example above on a Fedora system would+-- fail that way.+pickOS+ ::+ ( SingKind ('KProxy :: KProxy ka)+ , SingKind ('KProxy :: KProxy kb)+ , DemoteRep ('KProxy :: KProxy ka) ~ [MetaType]+ , DemoteRep ('KProxy :: KProxy kb) ~ [MetaType]+ , SingI c+ -- Would be nice to have this constraint, but+ -- union will not generate metatypes lists with the same+ -- order of OS's as is used everywhere else. So, + -- would need a type-level sort.+ --, Union a b ~ c+ )+ => Property (MetaTypes (a :: ka))+ -> Property (MetaTypes (b :: kb))+ -> Property (MetaTypes c)+pickOS a b = c `addChildren` [toChildProperty a, toChildProperty b]+ where+ -- This use of getSatisfy is safe, because both a and b+ -- are added as children, so their info will propigate.+ c = withOS (getDesc a) $ \_ o ->+ if matching o a+ then getSatisfy a+ else if matching o b+ then getSatisfy b+ else unsupportedOS'+ matching Nothing _ = False+ matching (Just o) p = + Targeting (systemToTargetOS o)+ `elem`+ fromSing (proptype p)+ proptype (Property t _ _ _ _) = t +-- | Makes a property that is satisfied differently depending on specifics+-- of the host's operating system.+--+-- > myproperty :: Property Debian+-- > myproperty = withOS "foo installed" $ \w o -> case o of+-- > (Just (System (Debian (Stable release)) arch)) -> ensureProperty w ...+-- > (Just (System (Debian suite) arch)) -> ensureProperty w ...+-- > _ -> unsupportedOS'+--+-- Note that the operating system specifics may not be declared for all hosts,+-- which is where Nothing comes in.+withOS+ :: (SingI metatypes)+ => Desc+ -> (OuterMetaTypesWitness '[] -> Maybe System -> Propellor Result)+ -> Property (MetaTypes metatypes)+withOS desc a = property desc $ a dummyoutermetatypes =<< getOS+ where+ -- Using this dummy value allows ensureProperty to be used+ -- even though the inner property probably doesn't target everything+ -- that the outer withOS property targets.+ dummyoutermetatypes :: OuterMetaTypesWitness ('[])+ dummyoutermetatypes = OuterMetaTypesWitness sing++-- | A property that always fails with an unsupported OS error.+unsupportedOS :: Property UnixLike+unsupportedOS = property "unsupportedOS" unsupportedOS'+ -- | Throws an error, for use in `withOS` when a property is lacking -- support for an OS.-unsupportedOS :: Propellor a-unsupportedOS = go =<< getOS- where- go Nothing = error "Unknown host OS is not supported by this property."- go (Just o) = error $ "This property is not implemented for " ++ show o+unsupportedOS' :: Propellor Result+unsupportedOS' = go =<< getOS+ where+ go Nothing = error "Unknown host OS is not supported by this property."+ go (Just o) = error $ "This property is not implemented for " ++ show o -- | Undoes the effect of a RevertableProperty.-revert :: RevertableProperty i -> RevertableProperty i+revert :: RevertableProperty setup undo -> RevertableProperty undo setup revert (RevertableProperty p1 p2) = RevertableProperty p2 p1 makeChange :: IO () -> Propellor Result@@ -279,7 +347,7 @@ noChange :: Propellor Result noChange = return NoChange -doNothing :: Property NoInfo+doNothing :: SingI t => Property (MetaTypes t) doNothing = property "noop property" noChange -- | Registers an action that should be run at the very end, after
src/Propellor/Property/Aiccu.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE TypeFamilies #-}+ -- | Maintainer: Jelmer Vernooij <jelmer@samba.org> module Propellor.Property.Aiccu (@@ -14,10 +16,10 @@ import qualified Propellor.Property.Service as Service import qualified Propellor.Property.File as File -installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.installed ["aiccu"] -restarted :: Property NoInfo+restarted :: Property DebianLike restarted = Service.restarted "aiccu" confPath :: FilePath@@ -41,12 +43,12 @@ -- | Configures an ipv6 tunnel using sixxs.net, with the given TunneId -- and sixx.net UserName.-hasConfig :: TunnelId -> UserName -> Property HasInfo-hasConfig t u = prop `onChange` restarted+hasConfig :: TunnelId -> UserName -> Property (HasInfo + DebianLike)+hasConfig t u = prop `onChange` restarted where+ prop :: Property (HasInfo + UnixLike) prop = withSomePrivData [(Password (u++"/"++t)), (Password u)] (Context "aiccu") $- property "aiccu configured" . writeConfig- writeConfig :: (((PrivDataField, PrivData) -> Propellor Result) -> Propellor Result) -> Propellor Result- writeConfig getpassword = getpassword $ ensureProperty . go+ property' "aiccu configured" . writeConfig+ writeConfig getpassword w = getpassword $ ensureProperty w . go go (Password u', p) = confPath `File.hasContentProtected` config u' t p go (f, _) = error $ "Unexpected type of privdata: " ++ show f
src/Propellor/Property/Apache.hs view
@@ -6,50 +6,50 @@ import qualified Propellor.Property.Service as Service import qualified Propellor.Property.LetsEncrypt as LetsEncrypt -installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.installed ["apache2"] -restarted :: Property NoInfo+restarted :: Property DebianLike restarted = Service.restarted "apache2" -reloaded :: Property NoInfo+reloaded :: Property DebianLike reloaded = Service.reloaded "apache2" type ConfigLine = String type ConfigFile = [ConfigLine] -siteEnabled :: Domain -> ConfigFile -> RevertableProperty NoInfo+siteEnabled :: Domain -> ConfigFile -> RevertableProperty DebianLike DebianLike siteEnabled domain cf = siteEnabled' domain cf <!> siteDisabled domain -siteEnabled' :: Domain -> ConfigFile -> Property NoInfo-siteEnabled' domain cf = combineProperties ("apache site enabled " ++ domain)- [ siteAvailable domain cf+siteEnabled' :: Domain -> ConfigFile -> Property DebianLike+siteEnabled' domain cf = combineProperties ("apache site enabled " ++ domain) $ props+ & siteAvailable domain cf `requires` installed `onChange` reloaded- , check (not <$> isenabled)+ & check (not <$> isenabled) (cmdProperty "a2ensite" ["--quiet", domain]) `requires` installed `onChange` reloaded- ] where isenabled = boolSystem "a2query" [Param "-q", Param "-s", Param domain] -siteDisabled :: Domain -> Property NoInfo+siteDisabled :: Domain -> Property DebianLike siteDisabled domain = combineProperties ("apache site disabled " ++ domain)- (map File.notPresent (siteCfg domain))+ (toProps $ map File.notPresent (siteCfg domain)) `onChange` (cmdProperty "a2dissite" ["--quiet", domain] `assume` MadeChange) `requires` installed `onChange` reloaded -siteAvailable :: Domain -> ConfigFile -> Property NoInfo+siteAvailable :: Domain -> ConfigFile -> Property DebianLike siteAvailable domain cf = combineProperties ("apache site available " ++ domain) $- map (`File.hasContent` (comment:cf)) (siteCfg domain)+ toProps $ map tightenTargets $+ map (`File.hasContent` (comment:cf)) (siteCfg domain) where comment = "# deployed with propellor, do not modify" -modEnabled :: String -> RevertableProperty NoInfo+modEnabled :: String -> RevertableProperty DebianLike DebianLike modEnabled modname = enable <!> disable where enable = check (not <$> isenabled)@@ -68,7 +68,7 @@ -- -- Note that ports are also specified inside a site's config file, -- so that also needs to be changed.-listenPorts :: [Port] -> Property NoInfo+listenPorts :: [Port] -> Property DebianLike listenPorts ps = "/etc/apache2/ports.conf" `File.hasContent` map portline ps `onChange` restarted where@@ -89,7 +89,7 @@ -- -- This was off by default in apache 2.2.22. Newver versions enable -- it by default. This property uses the filename used by the old version.-multiSSL :: Property NoInfo+multiSSL :: Property DebianLike multiSSL = check (doesDirectoryExist "/etc/apache2/conf.d") $ "/etc/apache2/conf.d/ssl" `File.hasContent` [ "NameVirtualHost *:443"@@ -129,11 +129,11 @@ -- | A basic virtual host, publishing a directory, and logging to -- the combined apache log file. Not https capable.-virtualHost :: Domain -> Port -> WebRoot -> RevertableProperty NoInfo+virtualHost :: Domain -> Port -> WebRoot -> RevertableProperty DebianLike DebianLike virtualHost domain port docroot = virtualHost' domain port docroot [] -- | Like `virtualHost` but with additional config lines added.-virtualHost' :: Domain -> Port -> WebRoot -> [ConfigLine] -> RevertableProperty NoInfo+virtualHost' :: Domain -> Port -> WebRoot -> [ConfigLine] -> RevertableProperty DebianLike DebianLike virtualHost' domain port docroot addedcfg = siteEnabled domain $ [ "<VirtualHost *:" ++ fromPort port ++ ">" , "ServerName " ++ domain ++ ":" ++ fromPort port@@ -159,11 +159,11 @@ -- -- Note that reverting this property does not remove the certificate from -- letsencrypt's cert store.-httpsVirtualHost :: Domain -> WebRoot -> LetsEncrypt.AgreeTOS -> RevertableProperty NoInfo+httpsVirtualHost :: Domain -> WebRoot -> LetsEncrypt.AgreeTOS -> RevertableProperty DebianLike DebianLike httpsVirtualHost domain docroot letos = httpsVirtualHost' domain docroot letos [] -- | Like `httpsVirtualHost` but with additional config lines added.-httpsVirtualHost' :: Domain -> WebRoot -> LetsEncrypt.AgreeTOS -> [ConfigLine] -> RevertableProperty NoInfo+httpsVirtualHost' :: Domain -> WebRoot -> LetsEncrypt.AgreeTOS -> [ConfigLine] -> RevertableProperty DebianLike DebianLike httpsVirtualHost' domain docroot letos addedcfg = setup <!> teardown where setup = setuphttp@@ -185,13 +185,13 @@ , "RewriteRule ^/(.*) https://" ++ domain ++ "/$1 [L,R,NE]" ] setuphttps = LetsEncrypt.letsEncrypt letos domain docroot- `onChange` combineProperties (domain ++ " ssl cert installed")- [ File.dirExists (takeDirectory cf)- , File.hasContent cf sslvhost- `onChange` reloaded- -- always reload since the cert has changed- , reloaded- ]+ `onChange` postsetuphttps+ postsetuphttps = combineProperties (domain ++ " ssl cert installed") $ props+ & File.dirExists (takeDirectory cf)+ & File.hasContent cf sslvhost+ `onChange` reloaded+ -- always reload since the cert has changed+ & reloaded where cf = sslconffile "letsencrypt" sslvhost = vhost (Port 443)
src/Propellor/Property/Apt.hs view
@@ -75,42 +75,41 @@ in [l, srcLine l] | otherwise = [] --- | Makes sources.list have a standard content using the mirror CDN,+-- | Makes sources.list have a standard content using the Debian mirror CDN, -- with the Debian suite configured by the os. -- -- Since the CDN is sometimes unreliable, also adds backup lines using -- kernel.org.-stdSourcesList :: Property NoInfo-stdSourcesList = withOS "standard sources.list" $ \o ->- case o of- (Just (System (Debian suite) _)) ->- ensureProperty $ stdSourcesListFor suite- _ -> error "os is not declared to be Debian"+stdSourcesList :: Property Debian+stdSourcesList = withOS "standard sources.list" $ \w o -> case o of+ (Just (System (Debian suite) _)) ->+ ensureProperty w $ stdSourcesListFor suite+ _ -> unsupportedOS' -stdSourcesListFor :: DebianSuite -> Property NoInfo+stdSourcesListFor :: DebianSuite -> Property Debian stdSourcesListFor suite = stdSourcesList' suite [] -- | Adds additional sources.list generators. -- -- Note that if a Property needs to enable an apt source, it's better -- to do so via a separate file in </etc/apt/sources.list.d/>-stdSourcesList' :: DebianSuite -> [SourcesGenerator] -> Property NoInfo-stdSourcesList' suite more = setSourcesList+stdSourcesList' :: DebianSuite -> [SourcesGenerator] -> Property Debian+stdSourcesList' suite more = tightenTargets $ setSourcesList (concatMap (\gen -> gen suite) generators) `describe` ("standard sources.list for " ++ show suite) where generators = [debCdn, kernelOrg, securityUpdates] ++ more -setSourcesList :: [Line] -> Property NoInfo+setSourcesList :: [Line] -> Property DebianLike setSourcesList ls = sourcesList `File.hasContent` ls `onChange` update -setSourcesListD :: [Line] -> FilePath -> Property NoInfo+setSourcesListD :: [Line] -> FilePath -> Property DebianLike setSourcesListD ls basename = f `File.hasContent` ls `onChange` update where f = "/etc/apt/sources.list.d/" ++ basename ++ ".list" -runApt :: [String] -> UncheckedProperty NoInfo-runApt ps = cmdPropertyEnv "apt-get" ps noninteractiveEnv+runApt :: [String] -> UncheckedProperty DebianLike+runApt ps = tightenTargets $ cmdPropertyEnv "apt-get" ps noninteractiveEnv noninteractiveEnv :: [(String, String)] noninteractiveEnv =@@ -118,68 +117,67 @@ , ("APT_LISTCHANGES_FRONTEND", "none") ] -update :: Property NoInfo-update = combineProperties ("apt update")- [ pendingConfigured- , runApt ["update"]+update :: Property DebianLike+update = combineProperties ("apt update") $ props+ & pendingConfigured+ & runApt ["update"] `assume` MadeChange- ] -- | Have apt upgrade packages, adding new packages and removing old as -- necessary.-upgrade :: Property NoInfo+upgrade :: Property DebianLike upgrade = upgrade' "dist-upgrade" -upgrade' :: String -> Property NoInfo-upgrade' p = combineProperties ("apt " ++ p)- [ pendingConfigured- , runApt ["-y", p]+upgrade' :: String -> Property DebianLike+upgrade' p = combineProperties ("apt " ++ p) $ props+ & pendingConfigured+ & runApt ["-y", p] `assume` MadeChange- ] -- | Have apt upgrade packages, but never add new packages or remove -- old packages. Not suitable for upgrading acrocess major versions -- of the distribution.-safeUpgrade :: Property NoInfo+safeUpgrade :: Property DebianLike safeUpgrade = upgrade' "upgrade" -- | Have dpkg try to configure any packages that are not fully configured.-pendingConfigured :: Property NoInfo-pendingConfigured = cmdPropertyEnv "dpkg" ["--configure", "--pending"] noninteractiveEnv- `assume` MadeChange- `describe` "dpkg configured pending"+pendingConfigured :: Property DebianLike+pendingConfigured = tightenTargets $+ cmdPropertyEnv "dpkg" ["--configure", "--pending"] noninteractiveEnv+ `assume` MadeChange+ `describe` "dpkg configured pending" type Package = String -installed :: [Package] -> Property NoInfo+installed :: [Package] -> Property DebianLike installed = installed' ["-y"] -installed' :: [String] -> [Package] -> Property NoInfo+installed' :: [String] -> [Package] -> Property DebianLike installed' params ps = robustly $ check (isInstallable ps) go `describe` unwords ("apt installed":ps) where go = runApt (params ++ ["install"] ++ ps) -installedBackport :: [Package] -> Property NoInfo-installedBackport ps = withOS desc $ \o -> case o of+installedBackport :: [Package] -> Property Debian+installedBackport ps = withOS desc $ \w o -> case o of (Just (System (Debian suite) _)) -> case backportSuite suite of- Nothing -> unsupportedOS- Just bs -> ensureProperty $+ Nothing -> unsupportedOS'+ Just bs -> ensureProperty w $ runApt (["install", "-t", bs, "-y"] ++ ps) `changesFile` dpkgStatus- _ -> unsupportedOS+ _ -> unsupportedOS' where desc = unwords ("apt installed backport":ps) -- | Minimal install of package, without recommends.-installedMin :: [Package] -> Property NoInfo+installedMin :: [Package] -> Property DebianLike installedMin = installed' ["--no-install-recommends", "-y"] -removed :: [Package] -> Property NoInfo+removed :: [Package] -> Property DebianLike removed ps = check (or <$> isInstalled' ps) (runApt (["-y", "remove"] ++ ps)) `describe` unwords ("apt removed":ps) -buildDep :: [Package] -> Property NoInfo+buildDep :: [Package] -> Property DebianLike buildDep ps = robustly $ go `changesFile` dpkgStatus `describe` unwords ("apt build-dep":ps)@@ -189,7 +187,7 @@ -- | Installs the build deps for the source package unpacked -- in the specifed directory, with a dummy package also -- installed so that autoRemove won't remove them.-buildDepIn :: FilePath -> Property NoInfo+buildDepIn :: FilePath -> Property DebianLike buildDepIn dir = cmdPropertyEnv "sh" ["-c", cmd] noninteractiveEnv `changesFile` dpkgStatus `requires` installedMin ["devscripts", "equivs"]@@ -198,14 +196,8 @@ -- | Package installation may fail becuse the archive has changed. -- Run an update in that case and retry.-robustly :: (Combines (Property i) (Property NoInfo)) => Property i -> Property i-robustly p = adjustPropertySatisfy p $ \satisfy -> do- r <- satisfy- if r == FailedChange- -- Safe to use ignoreInfo because we're re-running- -- the same property.- then ensureProperty $ ignoreInfo $ p `requires` update- else return r+robustly :: Property DebianLike -> Property DebianLike+robustly p = p `fallback` (update `before` p) isInstallable :: [Package] -> IO Bool isInstallable ps = do@@ -230,13 +222,13 @@ environ <- addEntry "LANG" "C" <$> getEnvironment readProcessEnv "apt-cache" ("policy":ps) (Just environ) -autoRemove :: Property NoInfo+autoRemove :: Property DebianLike autoRemove = runApt ["-y", "autoremove"] `changesFile` dpkgStatus `describe` "apt autoremove" -- | Enables unattended upgrades. Revert to disable.-unattendedUpgrades :: RevertableProperty NoInfo+unattendedUpgrades :: RevertableProperty DebianLike DebianLike unattendedUpgrades = enable <!> disable where enable = setup True@@ -255,11 +247,12 @@ | enabled = "true" | otherwise = "false" - configure = withOS "unattended upgrades configured" $ \o ->+ configure :: Property DebianLike+ configure = withOS "unattended upgrades configured" $ \w o -> case o of -- the package defaults to only upgrading stable (Just (System (Debian suite) _))- | not (isStable suite) -> ensureProperty $+ | not (isStable suite) -> ensureProperty w $ "/etc/apt/apt.conf.d/50unattended-upgrades" `File.containsLine` ("Unattended-Upgrade::Origins-Pattern { \"o=Debian,a="++showSuite suite++"\"; };")@@ -271,10 +264,13 @@ -- | Preseeds debconf values and reconfigures the package so it takes -- effect.-reConfigure :: Package -> [(DebconfTemplate, DebconfTemplateType, DebconfTemplateValue)] -> Property NoInfo-reConfigure package vals = reconfigure `requires` setselections- `describe` ("reconfigure " ++ package)+reConfigure :: Package -> [(DebconfTemplate, DebconfTemplateType, DebconfTemplateValue)] -> Property DebianLike+reConfigure package vals = tightenTargets $ + reconfigure + `requires` setselections+ `describe` ("reconfigure " ++ package) where+ setselections :: Property DebianLike setselections = property "preseed" $ if null vals then noChange@@ -291,7 +287,7 @@ -- -- Assumes that there is a 1:1 mapping between service names and apt -- package names.-serviceInstalledRunning :: Package -> Property NoInfo+serviceInstalledRunning :: Package -> Property DebianLike serviceInstalledRunning svc = Service.running svc `requires` installed [svc] data AptKey = AptKey@@ -299,10 +295,10 @@ , pubkey :: String } -trustsKey :: AptKey -> RevertableProperty NoInfo+trustsKey :: AptKey -> RevertableProperty DebianLike DebianLike trustsKey k = trustsKey' k <!> untrustKey k -trustsKey' :: AptKey -> Property NoInfo+trustsKey' :: AptKey -> Property DebianLike trustsKey' k = check (not <$> doesFileExist f) $ property desc $ makeChange $ do withHandle StdinHandle createProcessSuccess (proc "gpg" ["--no-default-keyring", "--keyring", f, "--import", "-"]) $ \h -> do@@ -313,21 +309,21 @@ desc = "apt trusts key " ++ keyname k f = aptKeyFile k -untrustKey :: AptKey -> Property NoInfo-untrustKey = File.notPresent . aptKeyFile+untrustKey :: AptKey -> Property DebianLike+untrustKey = tightenTargets . File.notPresent . aptKeyFile aptKeyFile :: AptKey -> FilePath aptKeyFile k = "/etc/apt/trusted.gpg.d" </> keyname k ++ ".gpg" -- | Cleans apt's cache of downloaded packages to avoid using up disk -- space.-cacheCleaned :: Property NoInfo-cacheCleaned = cmdProperty "apt-get" ["clean"]+cacheCleaned :: Property DebianLike+cacheCleaned = tightenTargets $ cmdProperty "apt-get" ["clean"] `assume` NoChange `describe` "apt cache cleaned" -- | Add a foreign architecture to dpkg and apt.-hasForeignArch :: String -> Property NoInfo+hasForeignArch :: String -> Property DebianLike hasForeignArch arch = check notAdded (add `before` update) `describe` ("dpkg has foreign architecture " ++ arch) where
+ src/Propellor/Property/Apt/PPA.hs view
@@ -0,0 +1,115 @@+-- | Maintainer: 2016 Evan Cofsky <evan@theunixman.com>+--+-- Personal Package Archives+module Propellor.Property.Apt.PPA where++import Data.List+import Control.Applicative+import Prelude+import Data.String.Utils+import Data.String (IsString(..))+import Propellor.Base+import qualified Propellor.Property.Apt as Apt++-- | Ensure software-properties-common is installed.+installed :: Property DebianLike+installed = Apt.installed ["software-properties-common"]++-- | Personal Package Archives are people's individual package+-- contributions to the Buntish distro. There's a well-known format for+-- representing them, and this type represents that. It's also an instance+-- of 'Show' and 'IsString' so it can work with 'OverloadedStrings'. +-- More on PPAs can be found at <https://help.launchpad.net/Packaging/PPA>+data PPA = PPA+ { ppaAccount :: String -- ^ The Launchpad account hosting this archive.+ , ppaArchive :: String -- ^ The name of the archive.+ } deriving (Eq, Ord)++instance Show PPA where+ show p = concat ["ppa:", ppaAccount p, "/", ppaArchive p]++instance IsString PPA where+ -- | Parse strings like "ppa:zfs-native/stable" into a PPA.+ fromString s =+ let+ [_, ppa] = split "ppa:" s+ [acct, arch] = split "/" ppa+ in+ PPA acct arch++-- | Adds a PPA to the local system repositories.+addPpa :: PPA -> Property DebianLike+addPpa p =+ cmdPropertyEnv "apt-add-repository" ["--yes", show p] Apt.noninteractiveEnv+ `assume` MadeChange+ `describe` ("Added PPA " ++ (show p))+ `requires` installed++-- | A repository key ID to be downloaded with apt-key.+data AptKeyId = AptKeyId+ { akiName :: String+ , akiId :: String+ , akiServer :: String+ } deriving (Eq, Ord)++instance Show AptKeyId where+ show k = unwords ["Apt Key", akiName k, akiId k, "from", akiServer k]++-- | Adds an 'AptKeyId' from the specified GPG server.+addKeyId :: AptKeyId -> Property DebianLike+addKeyId keyId =+ check keyTrusted akcmd+ `describe` (unwords ["Add third-party Apt key", show keyId])+ where+ akcmd =+ tightenTargets $ cmdProperty "apt-key" ["adv", "--keyserver", akiServer keyId, "--recv-keys", akiId keyId]+ keyTrusted =+ let+ pks ls = concatMap (drop 1 . split "/")+ $ concatMap (take 1 . drop 1 . words)+ $ filter (\l -> "pub" `isPrefixOf` l)+ $ lines ls+ nkid = take 8 (akiId keyId)+ in+ (isInfixOf [nkid] . pks) <$> readProcess "apt-key" ["list"]++-- | An Apt source line that apt-add-repository will just add to+-- sources.list. It's also an instance of both 'Show' and 'IsString' to make+-- using 'OverloadedStrings' in the configuration file easier.+--+-- | FIXME there's apparently an optional "options" fragment that I've+-- definitely not parsed here.+data AptSource = AptSource+ { asURL :: Apt.Url -- ^ The URL hosting the repository+ , asSuite :: String -- ^ The operating system suite+ , asComponents :: [String] -- ^ The list of components to install from this repository.+ } deriving (Eq, Ord)++instance Show AptSource where+ show asrc = unwords ["deb", asURL asrc, asSuite asrc, unwords . asComponents $ asrc]++instance IsString AptSource where+ fromString s =+ let+ url:suite:comps = drop 1 . words $ s+ in+ AptSource url suite comps++-- | A repository for apt-add-source, either a PPA or a regular repository line.+data AptRepository = AptRepositoryPPA PPA | AptRepositorySource AptSource++-- | Adds an 'AptRepository' using apt-add-source.+addRepository :: AptRepository -> Property DebianLike+addRepository (AptRepositoryPPA p) = addPpa p+addRepository (AptRepositorySource src) =+ check repoExists addSrc+ `describe` unwords ["Adding APT repository", show src]+ `requires` installed+ where+ allSourceLines =+ readProcess "/bin/sh" ["-c", "cat /etc/apt/sources.list /etc/apt/sources.list.d/*"]+ activeSources = map (\s -> fromString s :: AptSource )+ . filter (not . isPrefixOf "#")+ . filter (/= "") . lines <$> allSourceLines+ repoExists = isInfixOf [src] <$> activeSources+ addSrc = cmdProperty "apt-add-source" [show src]
src/Propellor/Property/Chroot.hs view
@@ -19,9 +19,11 @@ ) where import Propellor.Base+import Propellor.Container import Propellor.Types.CmdLine import Propellor.Types.Chroot import Propellor.Types.Info+import Propellor.Types.Core import Propellor.Property.Chroot.Util import qualified Propellor.Property.Debootstrap as Debootstrap import qualified Propellor.Property.Systemd.Core as Systemd@@ -40,24 +42,24 @@ data Chroot where Chroot :: ChrootBootstrapper b => FilePath -> b -> Host -> Chroot +instance IsContainer Chroot where+ containerProperties (Chroot _ _ h) = containerProperties h+ containerInfo (Chroot _ _ h) = containerInfo h+ setContainerProperties (Chroot loc b h) ps = Chroot loc b (setContainerProperties h ps)+ chrootSystem :: Chroot -> Maybe System-chrootSystem (Chroot _ _ h) = fromInfoVal (getInfo (hostInfo h))+chrootSystem = fromInfoVal . fromInfo . containerInfo instance Show Chroot where show c@(Chroot loc _ _) = "Chroot " ++ loc ++ " " ++ show (chrootSystem c) -instance PropAccum Chroot where- (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 -- Left error message.- buildchroot :: b -> Maybe System -> FilePath -> Either String (Property HasInfo)+ buildchroot :: b -> Maybe System -> FilePath -> Either String (Property Linux) -- | Use this to bootstrap a chroot by extracting a tarball. --@@ -68,14 +70,14 @@ data ChrootTarball = ChrootTarball FilePath instance ChrootBootstrapper ChrootTarball where- buildchroot (ChrootTarball tb) _ loc = Right $ extractTarball loc tb+ buildchroot (ChrootTarball tb) _ loc = Right $+ tightenTargets $ extractTarball loc tb -extractTarball :: FilePath -> FilePath -> Property HasInfo-extractTarball target src = toProp .- check (unpopulated target) $- cmdProperty "tar" params- `assume` MadeChange- `requires` File.dirExists target+extractTarball :: FilePath -> FilePath -> Property UnixLike+extractTarball target src = check (unpopulated target) $+ cmdProperty "tar" params+ `assume` MadeChange+ `requires` File.dirExists target where params = [ "-C"@@ -92,28 +94,27 @@ (Just s@(System (Debian _) _)) -> Right $ debootstrap s (Just s@(System (Buntish _) _)) -> Right $ debootstrap s (Just (System (FreeBSD _) _)) -> Left "FreeBSD not supported by debootstrap."- Nothing -> Left "Cannot debootstrap; `os` property not specified"+ Nothing -> Left "Cannot debootstrap; OS not specified" where 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. At a minimum,--- add the `os` property to specify the operating system to bootstrap.+-- add a property such as `osDebian` to specify the operating system+-- to bootstrap. ----- > debootstrapped Debootstrap.BuildD "/srv/chroot/ghc-dev"--- > & os (System (Debian Unstable) "amd64")+-- > debootstrapped Debootstrap.BuildD "/srv/chroot/ghc-dev" $ props+-- > & osDebian Unstable "amd64" -- > & Apt.installed ["ghc", "haskell-platform"] -- > & ...-debootstrapped :: Debootstrap.DebootstrapConfig -> FilePath -> Chroot+debootstrapped :: Debootstrap.DebootstrapConfig -> FilePath -> Props metatypes -> Chroot debootstrapped conf = bootstrapped (Debootstrapped conf) -- | Defines a Chroot at the given location, bootstrapped with the -- specified ChrootBootstrapper.-bootstrapped :: ChrootBootstrapper b => b -> FilePath -> Chroot-bootstrapped bootstrapper location = Chroot location bootstrapper h- where- h = Host location [] mempty+bootstrapped :: ChrootBootstrapper b => b -> FilePath -> Props metatypes -> Chroot+bootstrapped bootstrapper location ps = Chroot location bootstrapper (host location ps) -- | Ensures that the chroot exists and is provisioned according to its -- properties.@@ -121,43 +122,44 @@ -- Reverting this property removes the chroot. Anything mounted inside it -- is first unmounted. Note that it does not ensure that any processes -- that might be running inside the chroot are stopped.-provisioned :: Chroot -> RevertableProperty HasInfo+provisioned :: Chroot -> RevertableProperty (HasInfo + Linux) Linux provisioned c = provisioned' (propagateChrootInfo c) c False -provisioned' :: (Property HasInfo -> Property HasInfo) -> Chroot -> Bool -> RevertableProperty HasInfo+provisioned'+ :: (Property Linux -> Property (HasInfo + Linux))+ -> Chroot+ -> Bool+ -> RevertableProperty (HasInfo + Linux) Linux provisioned' propigator c@(Chroot loc bootstrapper _) systemdonly =- (propigator $ propertyList (chrootDesc c "exists") [setup])+ (propigator $ setup `describe` chrootDesc c "exists") <!>- (propertyList (chrootDesc c "removed") [teardown])+ (teardown `describe` chrootDesc c "removed") where+ setup :: Property Linux setup = propellChroot c (inChrootProcess (not systemdonly) c) systemdonly- `requires` toProp built+ `requires` built built = case buildchroot bootstrapper (chrootSystem c) loc of Right p -> p Left e -> cantbuild e - cantbuild e = infoProperty (chrootDesc c "built") (error e) mempty []+ cantbuild e = property (chrootDesc c "built") (error e) + teardown :: Property Linux 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'- where- p' = infoProperty- (propertyDesc p)- (propertySatisfy p)- (propertyInfo p <> chrootInfo c)- (propertyChildren p)+propagateChrootInfo :: Chroot -> Property Linux -> Property (HasInfo + Linux)+propagateChrootInfo c@(Chroot location _ _) p = propagateContainer location c $+ p `setInfoProperty` chrootInfo c chrootInfo :: Chroot -> Info 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 :: Chroot -> ([String] -> IO (CreateProcess, IO ())) -> Bool -> Property UnixLike propellChroot c@(Chroot loc _ _) mkproc systemdonly = property (chrootDesc c "provisioned") $ do let d = localdir </> shimdir c let me = localdir </> "propellor"@@ -205,7 +207,7 @@ chain hostlist (ChrootChain hn loc systemdonly onconsole) = case findHostNoAlias hostlist hn of Nothing -> errorMessage ("cannot find host " ++ hn)- Just parenthost -> case M.lookup loc (_chroots $ getInfo $ hostInfo parenthost) of+ Just parenthost -> case M.lookup loc (_chroots $ fromInfo $ hostInfo parenthost) of Nothing -> errorMessage ("cannot find chroot " ++ loc ++ " on host " ++ hn) Just h -> go h where@@ -213,11 +215,10 @@ changeWorkingDirectory localdir when onconsole forceConsole onlyProcess (provisioningLock loc) $ do- r <- runPropellor (setInChroot h) $ ensureProperties $+ r <- runPropellor (setInChroot h) $ ensureChildProperties $ if systemdonly- then [Systemd.installed]- else map ignoreInfo $- hostProperties h+ then [toChildProperty Systemd.installed]+ else hostProperties h flushConcurrentOutput putStrLn $ "\n" ++ show r chain _ _ = errorMessage "bad chain command"@@ -255,15 +256,17 @@ -- from being started, which is often something you want to prevent when -- building a chroot. ----- This is accomplished by installing a </usr/sbin/policy-rc.d> script--- that does not let any daemons be started by packages that use+-- On Debian, this is accomplished by installing a </usr/sbin/policy-rc.d>+-- script that does not let any daemons be started by packages that use -- invoke-rc.d. Reverting the property removes the script.-noServices :: RevertableProperty NoInfo+--+-- This property has no effect on non-Debian systems.+noServices :: RevertableProperty UnixLike UnixLike noServices = setup <!> teardown where f = "/usr/sbin/policy-rc.d" script = [ "#!/bin/sh", "exit 101" ]- setup = combineProperties "no services started"+ setup = combineProperties "no services started" $ toProps [ File.hasContent f script , File.mode f (combineModes (readModes ++ executeModes)) ]
src/Propellor/Property/Cmd.hs view
@@ -58,10 +58,10 @@ -- | A property that can be satisfied by running a command. -- -- The command must exit 0 on success.-cmdProperty :: String -> [String] -> UncheckedProperty NoInfo+cmdProperty :: String -> [String] -> UncheckedProperty UnixLike cmdProperty cmd params = cmdProperty' cmd params id -cmdProperty' :: String -> [String] -> (CreateProcess -> CreateProcess) -> UncheckedProperty NoInfo+cmdProperty' :: String -> [String] -> (CreateProcess -> CreateProcess) -> UncheckedProperty UnixLike cmdProperty' cmd params mkprocess = unchecked $ property desc $ liftIO $ cmdResult <$> boolSystem' cmd (map Param params) mkprocess where@@ -74,7 +74,7 @@ -- | A property that can be satisfied by running a command, -- with added environment variables in addition to the standard -- environment.-cmdPropertyEnv :: String -> [String] -> [(String, String)] -> UncheckedProperty NoInfo+cmdPropertyEnv :: String -> [String] -> [(String, String)] -> UncheckedProperty UnixLike cmdPropertyEnv cmd params env = unchecked $ property desc $ liftIO $ do env' <- addEntries env <$> getEnvironment cmdResult <$> boolSystemEnv cmd (map Param params) (Just env')@@ -85,14 +85,14 @@ type Script = [String] -- | A property that can be satisfied by running a script.-scriptProperty :: Script -> UncheckedProperty NoInfo+scriptProperty :: Script -> UncheckedProperty UnixLike scriptProperty script = cmdProperty "sh" ["-c", shellcmd] where shellcmd = intercalate " ; " ("set -e" : script) -- | A property that can satisfied by running a script -- as user (cd'd to their home directory).-userScriptProperty :: User -> Script -> UncheckedProperty NoInfo+userScriptProperty :: User -> Script -> UncheckedProperty UnixLike userScriptProperty (User user) script = cmdProperty "su" ["--shell", "/bin/sh", "-c", shellcmd, user] where shellcmd = intercalate " ; " ("set -e" : "cd" : script)
src/Propellor/Property/Concurrent.hs view
@@ -37,6 +37,8 @@ ) where import Propellor.Base+import Propellor.Types.Core+import Propellor.Types.MetaTypes import Control.Concurrent import qualified Control.Concurrent.Async as A@@ -77,8 +79,8 @@ -- -- The above example will run foo and bar concurrently, and once either of -- those 2 properties finishes, will start running baz.-concurrentList :: IO Int -> Desc -> PropList -> Property HasInfo-concurrentList getn d (PropList ps) = infoProperty d go mempty ps+concurrentList :: SingI metatypes => IO Int -> Desc -> Props (MetaTypes metatypes) -> Property (MetaTypes metatypes)+concurrentList getn d (Props ps) = property d go `addChildren` ps where go = do n <- liftIO getn@@ -97,15 +99,11 @@ (p:rest) -> return (rest, Just p) case v of Nothing -> return r- -- This use of propertySatisfy does not lose any- -- Info asociated with the property, because- -- concurrentList sets all the properties as- -- children, and so propigates their info. Just p -> do hn <- asks hostName r' <- actionMessageOn hn- (propertyDesc p)- (propertySatisfy p)+ (getDesc p)+ (getSatisfy p) worker q (r <> r') -- | Run an action with the number of capabiities increased as necessary to
src/Propellor/Property/Conductor.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE FlexibleInstances, DeriveDataTypeable, GeneralizedNewtypeDeriving #-}+{-# LANGUAGE FlexibleInstances, DeriveDataTypeable, GeneralizedNewtypeDeriving, TypeFamilies #-} -- | This module adds conductors to propellor. A conductor is a Host that -- is responsible for running propellor on other hosts@@ -73,7 +73,8 @@ Conductable(..), ) where -import Propellor.Base hiding (os)+import Propellor.Base+import Propellor.Container import Propellor.Spin (spin') import Propellor.PrivData.Paths import Propellor.Types.Info@@ -82,21 +83,22 @@ import qualified Data.Set as S -- | Class of things that can be conducted.+--+-- There are instances for single hosts, and for lists of hosts.+-- With a list, each listed host will be conducted in turn. Failure to conduct+-- one host does not prevent conducting subsequent hosts in the list, but+-- will be propagated as an overall failure of the property. class Conductable c where- conducts :: c -> RevertableProperty HasInfo+ conducts :: c -> RevertableProperty (HasInfo + UnixLike) (HasInfo + UnixLike) instance Conductable Host where- -- | Conduct the specified host. conducts h = conductorFor h <!> notConductorFor h --- | Each host in the list will be conducted in turn. Failure to conduct--- one host does not prevent conducting subsequent hosts in the list, but--- will be propagated as an overall failure of the property. instance Conductable [Host] where conducts hs = - propertyList desc (map (toProp . conducts) hs)+ propertyList desc (toProps $ map (setupRevertableProperty . conducts) hs) <!>- propertyList desc (map (toProp . revert . conducts) hs)+ propertyList desc (toProps $ map (undoRevertableProperty . conducts) hs) where desc = cdesc $ unwords $ map hostName hs @@ -126,7 +128,7 @@ where go seen h | S.member (hostName h) seen = Nothing -- break loop- | otherwise = Just $ case getInfo (hostInfo h) of+ | otherwise = Just $ case fromInfo (hostInfo h) of ConductorFor [] -> Conducted h ConductorFor l -> let seen' = S.insert (hostName h) seen@@ -214,14 +216,15 @@ orchestrate hs = map go hs where go h- | isOrchestrated (getInfo (hostInfo h)) = h+ | isOrchestrated (fromInfo (hostInfo h)) = h | otherwise = foldl orchestrate' (removeold h) (map (deloop h) os) os = extractOrchestras hs removeold h = foldl removeold' h (oldconductorsof h)- removeold' h oldconductor = h & revert (conductedBy oldconductor)+ removeold' h oldconductor = setContainerProps h $ containerProps h+ ! conductedBy oldconductor - oldconductors = zip hs (map (getInfo . hostInfo) hs)+ oldconductors = zip hs (map (fromInfo . hostInfo) hs) oldconductorsof h = flip mapMaybe oldconductors $ \(oldconductor, NotConductorFor l) -> if any (sameHost h) l@@ -232,7 +235,9 @@ orchestrate' h (Conducted _) = h orchestrate' h (Conductor c l) | sameHost h c = cont $ addConductorPrivData h (concatMap allHosts l)- | any (sameHost h) (map topHost l) = cont $ h & conductedBy c+ | any (sameHost h) (map topHost l) = cont $+ setContainerProps h $ containerProps h+ & conductedBy c | otherwise = cont h where cont h' = foldl orchestrate' h' l@@ -240,14 +245,16 @@ -- The host this property is added to becomes the conductor for the -- specified Host. Note that `orchestrate` must be used for this property -- to have any effect.-conductorFor :: Host -> Property HasInfo-conductorFor h = infoProperty desc go (addInfo mempty (ConductorFor [h])) []- `requires` toProp (conductorKnownHost h)+conductorFor :: Host -> Property (HasInfo + UnixLike)+conductorFor h = go+ `setInfoProperty` (toInfo (ConductorFor [h]))+ `requires` setupRevertableProperty (conductorKnownHost h) `requires` Ssh.installed where desc = cdesc (hostName h) - go = ifM (isOrchestrated <$> askInfo)+ go :: Property UnixLike+ go = property desc $ ifM (isOrchestrated <$> askInfo) ( do pm <- liftIO $ filterPrivData h <$> readPrivDataFile privDataLocal@@ -262,13 +269,15 @@ ) -- Reverts conductorFor.-notConductorFor :: Host -> Property HasInfo-notConductorFor h = infoProperty desc (return NoChange) (addInfo mempty (NotConductorFor [h])) []- `requires` toProp (revert (conductorKnownHost h))+notConductorFor :: Host -> Property (HasInfo + UnixLike)+notConductorFor h = (doNothing :: Property UnixLike)+ `setInfoProperty` (toInfo (NotConductorFor [h]))+ `describe` desc+ `requires` undoRevertableProperty (conductorKnownHost h) where desc = "not " ++ cdesc (hostName h) -conductorKnownHost :: Host -> RevertableProperty NoInfo+conductorKnownHost :: Host -> RevertableProperty UnixLike UnixLike conductorKnownHost h = mk Ssh.knownHost <!>@@ -287,10 +296,10 @@ i = mempty `addInfo` mconcat (map privinfo hs) `addInfo` Orchestrated (Any True)- privinfo h' = forceHostContext (hostName h') $ getInfo (hostInfo h')+ privinfo h' = forceHostContext (hostName h') $ fromInfo (hostInfo h') -- Use this property to let the specified conductor ssh in and run propellor.-conductedBy :: Host -> RevertableProperty NoInfo+conductedBy :: Host -> RevertableProperty UnixLike UnixLike conductedBy h = (setup <!> teardown) `describe` ("conducted by " ++ hostName h) where
src/Propellor/Property/ConfFile.hs view
@@ -37,7 +37,7 @@ -> AdjustSection -> InsertSection -> FilePath- -> Property NoInfo+ -> Property UnixLike adjustSection desc start past adjust insert = fileProperty desc go where go ls = let (pre, wanted, post) = foldl' find ([], [], []) ls@@ -68,7 +68,7 @@ -> AdjustSection -> InsertSection -> FilePath- -> Property NoInfo+ -> Property UnixLike adjustIniSection desc header = adjustSection desc@@ -77,7 +77,7 @@ -- | Ensures that a .ini file exists and contains a section -- with a key=value setting.-containsIniSetting :: FilePath -> (IniSection, IniKey, String) -> Property NoInfo+containsIniSetting :: FilePath -> (IniSection, IniKey, String) -> Property UnixLike containsIniSetting f (header, key, value) = adjustIniSection (f ++ " section [" ++ header ++ "] contains " ++ key ++ "=" ++ value)@@ -93,7 +93,7 @@ isKeyVal x = (filter (/= ' ') . takeWhile (/= '=')) x `elem` [key, '#':key] -- | Ensures that a .ini file does not contain the specified section.-lacksIniSection :: FilePath -> IniSection -> Property NoInfo+lacksIniSection :: FilePath -> IniSection -> Property UnixLike lacksIniSection f header = adjustIniSection (f ++ " lacks section [" ++ header ++ "]")
src/Propellor/Property/Cron.hs view
@@ -27,9 +27,11 @@ -- job file. -- -- The cron job's output will only be emailed if it exits nonzero.-job :: Desc -> Times -> User -> FilePath -> String -> Property NoInfo-job desc times (User u) cddir command = combineProperties ("cronned " ++ desc)- [ cronjobfile `File.hasContent`+job :: Desc -> Times -> User -> FilePath -> String -> Property DebianLike+job desc times (User u) cddir command = combineProperties ("cronned " ++ desc) $ props+ & Apt.serviceInstalledRunning "cron"+ & Apt.installed ["util-linux", "moreutils"]+ & cronjobfile `File.hasContent` [ case times of Times _ -> "" _ -> "#!/bin/sh\nset -e"@@ -44,22 +46,19 @@ "root" -> "chronic " ++ shellEscape scriptfile _ -> "chronic su " ++ u ++ " -c " ++ shellEscape scriptfile ]- , case times of+ & case times of Times _ -> doNothing _ -> cronjobfile `File.mode` combineModes (readModes ++ executeModes) -- Use a separate script because it makes the cron job name -- prettier in emails, and also allows running the job manually.- , scriptfile `File.hasContent`+ & scriptfile `File.hasContent` [ "#!/bin/sh" , "# Generated by propellor" , "set -e" , "flock -n " ++ shellEscape cronjobfile ++ " sh -c " ++ shellEscape cmdline ]- , scriptfile `File.mode` combineModes (readModes ++ executeModes)- ]- `requires` Apt.serviceInstalledRunning "cron"- `requires` Apt.installed ["util-linux", "moreutils"]+ & scriptfile `File.mode` combineModes (readModes ++ executeModes) where cmdline = "cd " ++ cddir ++ " && ( " ++ command ++ " )" cronjobfile = "/etc" </> cronjobdir </> name@@ -75,13 +74,13 @@ | otherwise = '_' -- | Installs a cron job, and runs it niced and ioniced.-niceJob :: Desc -> Times -> User -> FilePath -> String -> Property NoInfo+niceJob :: Desc -> Times -> User -> FilePath -> String -> Property DebianLike niceJob desc times user cddir command = job desc times user cddir ("nice ionice -c 3 sh -c " ++ shellEscape command) -- | Installs a cron job to run propellor.-runPropellor :: Times -> Property NoInfo-runPropellor times = withOS "propellor cron job" $ \o -> - ensureProperty $+runPropellor :: Times -> Property UnixLike+runPropellor times = withOS "propellor cron job" $ \w o -> + ensureProperty w $ niceJob "propellor" times (User "root") localdir (bootstrapPropellorCommand o ++ "; ./propellor")
src/Propellor/Property/DebianMirror.hs view
@@ -119,19 +119,17 @@ debianMirrorRsyncExtra :: [RsyncExtra] -> DebianMirror -> DebianMirror debianMirrorRsyncExtra r m = m { _debianMirrorRsyncExtra = r } -mirror :: DebianMirror -> Property NoInfo-mirror mirror' = propertyList- ("Debian mirror " ++ dir)- [ Apt.installed ["debmirror"]- , User.accountFor (User "debmirror")- , File.dirExists dir- , File.ownerGroup dir (User "debmirror") (Group "debmirror")- , check (not . and <$> mapM suitemirrored suites)+mirror :: DebianMirror -> Property DebianLike+mirror mirror' = propertyList ("Debian mirror " ++ dir) $ props+ & Apt.installed ["debmirror"]+ & User.accountFor (User "debmirror")+ & File.dirExists dir+ & File.ownerGroup dir (User "debmirror") (Group "debmirror")+ & check (not . and <$> mapM suitemirrored suites) (cmdProperty "debmirror" args) `describe` "debmirror setup"- , Cron.niceJob ("debmirror_" ++ dir) (_debianMirrorCronTimes mirror') (User "debmirror") "/" $- unwords ("/usr/bin/debmirror" : args)- ]+ & Cron.niceJob ("debmirror_" ++ dir) (_debianMirrorCronTimes mirror') (User "debmirror") "/"+ (unwords ("/usr/bin/debmirror" : args)) where dir = _debianMirrorDir mirror' suites = _debianMirrorSuites mirror'
src/Propellor/Property/Debootstrap.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE FlexibleContexts #-}- module Propellor.Property.Debootstrap ( Url, DebootstrapConfig(..),@@ -48,14 +46,15 @@ -- -- The System can be any OS and architecture that debootstrap -- and the kernel support.-built :: FilePath -> System -> DebootstrapConfig -> Property HasInfo-built target system config = built' (toProp installed) target system config+built :: FilePath -> System -> DebootstrapConfig -> Property Linux+built target system config = built' (setupRevertableProperty installed) target system config -built' :: (Combines (Property NoInfo) (Property i)) => Property i -> FilePath -> System -> DebootstrapConfig -> Property (CInfo NoInfo i)+built' :: Property Linux -> FilePath -> System -> DebootstrapConfig -> Property Linux built' installprop target system@(System _ arch) config = check (unpopulated target <||> ispartial) setupprop `requires` installprop where+ setupprop :: Property Linux setupprop = property ("debootstrapped " ++ target) $ liftIO $ do createDirectoryIfMissing True target -- Don't allow non-root users to see inside the chroot,@@ -99,39 +98,34 @@ -- When necessary, falls back to installing debootstrap from source. -- Note that installation from source is done by downloading the tarball -- from a Debian mirror, with no cryptographic verification.-installed :: RevertableProperty NoInfo+installed :: RevertableProperty Linux Linux installed = install <!> remove where- install = withOS "debootstrap installed" $ \o ->- ifM (liftIO $ isJust <$> programPath)- ( return NoChange- , ensureProperty (installon o)- )-- installon (Just (System (Debian _) _)) = aptinstall- installon (Just (System (Buntish _) _)) = aptinstall- installon _ = sourceInstall+ install = check (isJust <$> programPath) $+ (aptinstall `pickOS` sourceInstall)+ `describe` "debootstrap installed" - remove = withOS "debootstrap removed" $ ensureProperty . removefrom- removefrom (Just (System (Debian _) _)) = aptremove- removefrom (Just (System (Buntish _) _)) = aptremove- removefrom _ = sourceRemove+ remove = (aptremove `pickOS` sourceRemove)+ `describe` "debootstrap removed" aptinstall = Apt.installed ["debootstrap"] aptremove = Apt.removed ["debootstrap"] -sourceInstall :: Property NoInfo-sourceInstall = property "debootstrap installed from source" (liftIO sourceInstall')+sourceInstall :: Property Linux+sourceInstall = go `requires` perlInstalled `requires` arInstalled+ where+ go :: Property Linux+ go = property "debootstrap installed from source" (liftIO sourceInstall') -perlInstalled :: Property NoInfo+perlInstalled :: Property Linux perlInstalled = check (not <$> inPath "perl") $ property "perl installed" $ liftIO $ toResult . isJust <$> firstM id [ yumInstall "perl" ] -arInstalled :: Property NoInfo+arInstalled :: Property Linux arInstalled = check (not <$> inPath "ar") $ property "ar installed" $ liftIO $ toResult . isJust <$> firstM id [ yumInstall "binutils"@@ -175,7 +169,7 @@ return MadeChange _ -> errorMessage "debootstrap tar file did not contain exactly one dirctory" -sourceRemove :: Property NoInfo+sourceRemove :: Property Linux sourceRemove = property "debootstrap not installed from source" $ liftIO $ ifM (doesDirectoryExist sourceInstallDir) ( do
src/Propellor/Property/DiskImage.hs view
@@ -2,6 +2,8 @@ -- -- This module is designed to be imported unqualified. +{-# LANGUAGE TypeFamilies #-}+ module Propellor.Property.DiskImage ( -- * Partition specification module Propellor.Property.DiskImage.PartSpec,@@ -30,6 +32,7 @@ import Propellor.Property.Mount import Propellor.Property.Partition import Propellor.Property.Rsync+import Propellor.Container import Utility.Path import Data.List (isPrefixOf, isInfixOf, sortBy)@@ -51,7 +54,8 @@ -- -- > import Propellor.Property.DiskImage ----- > let chroot d = Chroot.debootstrapped (System (Debian Unstable) "amd64") mempty d+-- > let chroot d = Chroot.debootstrapped mempty d+-- > & osDebian Unstable "amd64" -- > & Apt.installed ["linux-image-amd64"] -- > & User.hasPassword (User "root") -- > & User.accountFor (User "demo")@@ -76,44 +80,54 @@ -- chroot while the disk image is being built, which should prevent any -- daemons that are included from being started on the system that is -- building the disk image.-imageBuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty HasInfo+imageBuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty (HasInfo + Linux) 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+imageRebuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty (HasInfo + Linux) Linux imageRebuilt = imageBuilt' True -imageBuilt' :: Bool -> DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty HasInfo+imageBuilt' :: Bool -> DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty (HasInfo + Linux) Linux imageBuilt' rebuild img mkchroot tabletype final partspec = imageBuiltFrom img chrootdir tabletype final partspec `requires` Chroot.provisioned chroot- `requires` (cleanrebuild <!> doNothing)+ `requires` (cleanrebuild <!> (doNothing :: Property UnixLike)) `describe` desc where desc = "built disk image " ++ img+ cleanrebuild :: Property Linux cleanrebuild | rebuild = property desc $ do liftIO $ removeChroot chrootdir return MadeChange | otherwise = doNothing chrootdir = img ++ ".chroot"- chroot = mkchroot chrootdir- -- Before ensuring any other properties of the chroot, avoid- -- starting services. Reverted by imageFinalized.- &^ Chroot.noServices- -- First stage finalization.- & fst final- -- Avoid wasting disk image space on the apt cache- & Apt.cacheCleaned+ chroot =+ let c = mkchroot chrootdir+ in setContainerProps c $ containerProps c+ -- Before ensuring any other properties of the chroot,+ -- avoid starting services. Reverted by imageFinalized.+ &^ Chroot.noServices+ -- First stage finalization.+ & fst final+ & cachesCleaned +-- | This property is automatically added to the chroot when building a+-- disk image. It cleans any caches of information that can be omitted;+-- eg the apt cache on Debian.+cachesCleaned :: Property UnixLike+cachesCleaned = "cache cleaned" ==> (Apt.cacheCleaned `pickOS` skipit)+ where+ skipit = doNothing :: Property UnixLike+ -- | Builds a disk image from the contents of a chroot.-imageBuiltFrom :: DiskImage -> FilePath -> TableType -> Finalization -> [PartSpec] -> RevertableProperty NoInfo+imageBuiltFrom :: DiskImage -> FilePath -> TableType -> Finalization -> [PartSpec] -> RevertableProperty (HasInfo + Linux) UnixLike imageBuiltFrom img chrootdir tabletype final partspec = mkimg <!> rmimg where desc = img ++ " built from " ++ chrootdir- mkimg = property desc $ do+ mkimg = property' desc $ \w -> do -- unmount helper filesystems such as proc from the chroot -- before getting sizes liftIO $ unmountBelow chrootdir@@ -123,7 +137,7 @@ -- tie the knot! let (mnts, mntopts, parttable) = fitChrootSize tabletype partspec $ map (calcsz mnts) mnts- ensureProperty $+ ensureProperty w $ imageExists img (partTableSize parttable) `before` partitioned YesReallyDeleteDiskContents img parttable@@ -135,17 +149,18 @@ imageFinalized final mnts mntopts devs parttable rmimg = File.notPresent img -partitionsPopulated :: FilePath -> [Maybe MountPoint] -> [MountOpts] -> [LoopDev] -> Property NoInfo-partitionsPopulated chrootdir mnts mntopts devs = property desc $ mconcat $ zipWith3 go mnts mntopts devs+partitionsPopulated :: FilePath -> [Maybe MountPoint] -> [MountOpts] -> [LoopDev] -> Property Linux+partitionsPopulated chrootdir mnts mntopts devs = property' desc $ \w -> + mconcat $ zipWith3 (go w) mnts mntopts devs where desc = "partitions populated from " ++ chrootdir - go Nothing _ _ = noChange- go (Just mnt) mntopt loopdev = withTmpDir "mnt" $ \tmpdir -> bracket+ go _ Nothing _ _ = noChange+ go w (Just mnt) mntopt loopdev = withTmpDir "mnt" $ \tmpdir -> bracket (liftIO $ mount "auto" (partitionLoopDev loopdev) tmpdir mntopt) (const $ liftIO $ umountLazy tmpdir) $ \ismounted -> if ismounted- then ensureProperty $+ then ensureProperty w $ syncDirFiltered (filtersfor mnt) (chrootdir ++ mnt) tmpdir else return FailedChange @@ -203,7 +218,7 @@ -- 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 :: FilePath -> ByteSize -> Property Linux imageExists img sz = property ("disk image exists" ++ img) $ liftIO $ do ms <- catchMaybeIO $ getFileStatus img case ms of@@ -226,19 +241,19 @@ -- -- It's ok if the second property leaves additional things mounted -- in the partition tree.-type Finalization = (Property NoInfo, (FilePath -> [LoopDev] -> Property NoInfo))+type Finalization = (Property Linux, (FilePath -> [LoopDev] -> Property Linux)) -imageFinalized :: Finalization -> [Maybe MountPoint] -> [MountOpts] -> [LoopDev] -> PartTable -> Property NoInfo+imageFinalized :: Finalization -> [Maybe MountPoint] -> [MountOpts] -> [LoopDev] -> PartTable -> Property Linux imageFinalized (_, final) mnts mntopts devs (PartTable _ parts) = - property "disk image finalized" $ + property' "disk image finalized" $ \w -> withTmpDir "mnt" $ \top -> - go top `finally` liftIO (unmountall top)+ go w top `finally` liftIO (unmountall top) where- go top = do+ go w top = do liftIO $ mountall top liftIO $ writefstab top liftIO $ allowservices top- ensureProperty $ final top devs+ ensureProperty w $ final top devs -- Ordered lexographically by mount point, so / comes before /usr -- comes before /usr/local@@ -280,27 +295,26 @@ grubBooted :: Grub.BIOS -> Finalization grubBooted bios = (Grub.installed' bios, boots) where- boots mnt loopdevs = combineProperties "disk image boots using grub"+ boots mnt loopdevs = combineProperties "disk image boots using grub" $ props -- 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+ & 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"]+ & inchroot "update-initramfs" ["-u"] `assume` MadeChange -- work around for http://bugs.debian.org/802717- , check haveosprober $ inchroot "chmod" ["-x", osprober]- , inchroot "update-grub" []+ & check haveosprober (inchroot "chmod" ["-x", osprober])+ & inchroot "update-grub" [] `assume` MadeChange- , check haveosprober $ inchroot "chmod" ["+x", osprober]- , inchroot "grub-install" [wholediskloopdev]+ & check haveosprober (inchroot "chmod" ["+x", osprober])+ & inchroot "grub-install" [wholediskloopdev] `assume` MadeChange -- 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" []+ & cmdProperty "sync" [] `assume` NoChange- ] where -- cannot use </> since the filepath is absolute inmnt f = mnt ++ f
src/Propellor/Property/Dns.hs view
@@ -60,7 +60,7 @@ -- -- In either case, the secondary dns server Host should have an ipv4 and/or -- ipv6 property defined.-primary :: [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty HasInfo+primary :: [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty (HasInfo + DebianLike) DebianLike primary hosts domain soa rs = setup <!> cleanup where setup = setupPrimary zonefile id hosts domain soa rs@@ -70,7 +70,7 @@ zonefile = "/etc/bind/propellor/db." ++ domain -setupPrimary :: FilePath -> (FilePath -> FilePath) -> [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> Property HasInfo+setupPrimary :: FilePath -> (FilePath -> FilePath) -> [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> Property (HasInfo + DebianLike) setupPrimary zonefile mknamedconffile hosts domain soa rs = withwarnings baseprop `requires` servingZones@@ -80,9 +80,10 @@ indomain = M.elems $ M.filterWithKey (\hn _ -> inDomain domain $ AbsDomain $ hn) hostmap (partialzone, zonewarnings) = genZone indomain hostmap domain soa- baseprop = infoProperty ("dns primary for " ++ domain) satisfy- (mempty `addInfo` addNamedConf conf) []- satisfy = do+ baseprop = primaryprop+ `setInfoProperty` (toInfo (addNamedConf conf))+ primaryprop :: Property DebianLike+ primaryprop = property ("dns primary for " ++ domain) $ do sshfps <- concat <$> mapM (genSSHFP domain) (M.elems hostmap) let zone = partialzone { zHosts = zHosts partialzone ++ rs ++ sshfps }@@ -120,11 +121,13 @@ in z /= oldzone || oldserial < sSerial (zSOA zone) -cleanupPrimary :: FilePath -> Domain -> Property NoInfo+cleanupPrimary :: FilePath -> Domain -> Property DebianLike cleanupPrimary zonefile domain = check (doesFileExist zonefile) $- property ("removed dns primary for " ++ domain)- (makeChange $ removeZoneFile zonefile)- `requires` namedConfWritten+ go `requires` namedConfWritten+ where+ desc = "removed dns primary for " ++ domain+ go :: Property DebianLike+ go = property desc (makeChange $ removeZoneFile zonefile) -- | Primary dns server for a domain, secured with DNSSEC. --@@ -152,7 +155,7 @@ -- This is different from the serial number used by 'primary', so if you -- want to later disable DNSSEC you will need to adjust the serial number -- passed to mkSOA to ensure it is larger.-signedPrimary :: Recurrance -> [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty HasInfo+signedPrimary :: Recurrance -> [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty (HasInfo + DebianLike) DebianLike signedPrimary recurrance hosts domain soa rs = setup <!> cleanup where setup = combineProperties ("dns primary for " ++ domain ++ " (signed)")@@ -184,12 +187,12 @@ -- -- Note that if a host is declared to be a primary and a secondary dns -- server for the same domain, the primary server config always wins.-secondary :: [Host] -> Domain -> RevertableProperty HasInfo+secondary :: [Host] -> Domain -> RevertableProperty (HasInfo + DebianLike) DebianLike secondary hosts domain = secondaryFor (otherServers Master hosts domain) hosts domain -- | This variant is useful if the primary server does not have its DNS -- configured via propellor.-secondaryFor :: [HostName] -> [Host] -> Domain -> RevertableProperty HasInfo+secondaryFor :: [HostName] -> [Host] -> Domain -> RevertableProperty (HasInfo + DebianLike) DebianLike secondaryFor masters hosts domain = setup <!> cleanup where setup = pureInfoProperty desc (addNamedConf conf)@@ -210,7 +213,7 @@ otherServers wantedtype hosts domain = M.keys $ M.filter wanted $ hostMap hosts where- wanted h = case M.lookup domain (fromNamedConfMap $ getInfo $ hostInfo h) of+ wanted h = case M.lookup domain (fromNamedConfMap $ fromInfo $ hostInfo h) of Nothing -> False Just conf -> confDnsServerType conf == wantedtype && confDomain conf == domain@@ -218,15 +221,15 @@ -- | Rewrites the whole named.conf.local file to serve the zones -- configured by `primary` and `secondary`, and ensures that bind9 is -- running.-servingZones :: Property NoInfo+servingZones :: Property DebianLike servingZones = namedConfWritten `onChange` Service.reloaded "bind9" `requires` Apt.serviceInstalledRunning "bind9" -namedConfWritten :: Property NoInfo-namedConfWritten = property "named.conf configured" $ do+namedConfWritten :: Property DebianLike+namedConfWritten = property' "named.conf configured" $ \w -> do zs <- getNamedConf- ensureProperty $+ ensureProperty w $ hasContent namedConfFile $ concatMap confStanza $ M.elems zs @@ -465,7 +468,7 @@ -- So we can just use the IPAddrs. addcnames :: Host -> [Either WarningMessage (BindDomain, Record)] addcnames h = concatMap gen $ filter (inDomain zdomain) $- mapMaybe getCNAME $ S.toList $ fromDnsInfo $ getInfo info+ mapMaybe getCNAME $ S.toList $ fromDnsInfo $ fromInfo info where info = hostInfo h gen c = case getAddresses info of@@ -480,7 +483,7 @@ where info = hostInfo h l = zip (repeat $ AbsDomain $ hostName h)- (S.toList $ S.filter (\r -> isNothing (getIPAddr r) && isNothing (getCNAME r)) (fromDnsInfo $ getInfo info))+ (S.toList $ S.filter (\r -> isNothing (getIPAddr r) && isNothing (getCNAME r)) (fromDnsInfo $ fromInfo info)) -- Simplifies the list of hosts. Remove duplicate entries. -- Also, filter out any CHAMES where the same domain has an@@ -515,7 +518,7 @@ domain = confDomain conf getNamedConf :: Propellor (M.Map Domain NamedConf)-getNamedConf = asks $ fromNamedConfMap . getInfo . hostInfo+getNamedConf = asks $ fromNamedConfMap . fromInfo . hostInfo -- | Generates SSHFP records for hosts in the domain (or with CNAMES -- in the domain) that have configured ssh public keys.@@ -528,7 +531,7 @@ gen = liftIO . mapM genSSHFP' . M.elems . fromMaybe M.empty mk r = mapMaybe (\d -> if inDomain domain d then Just (d, r) else Nothing) (AbsDomain hostname : cnames)- cnames = mapMaybe getCNAME $ S.toList $ fromDnsInfo $ getInfo info+ cnames = mapMaybe getCNAME $ S.toList $ fromDnsInfo $ fromInfo info hostname = hostName h info = hostInfo h
src/Propellor/Property/DnsSec.hs view
@@ -7,13 +7,13 @@ -- -- signedPrimary uses this, so this property does not normally need to be -- used directly.-keysInstalled :: Domain -> RevertableProperty HasInfo+keysInstalled :: Domain -> RevertableProperty (HasInfo + UnixLike) UnixLike keysInstalled domain = setup <!> cleanup where- setup = propertyList "DNSSEC keys installed" $+ setup = propertyList "DNSSEC keys installed" $ toProps $ map installkey keys - cleanup = propertyList "DNSSEC keys removed" $+ cleanup = propertyList "DNSSEC keys removed" $ toProps $ map (File.notPresent . keyFn domain) keys installkey k = writer (keysrc k) (keyFn domain k) (Context domain)@@ -37,12 +37,14 @@ -- -- signedPrimary uses this, so this property does not normally need to be -- used directly.-zoneSigned :: Domain -> FilePath -> RevertableProperty HasInfo+zoneSigned :: Domain -> FilePath -> RevertableProperty (HasInfo + UnixLike) UnixLike zoneSigned domain zonefile = setup <!> cleanup where+ setup :: Property (HasInfo + UnixLike) setup = check needupdate (forceZoneSigned domain zonefile) `requires` keysInstalled domain + cleanup :: Property UnixLike cleanup = File.notPresent (signedZoneFile zonefile) `before` File.notPresent dssetfile `before` revert (keysInstalled domain)@@ -63,7 +65,7 @@ t2 <- getModificationTime f return (t2 >= t1) -forceZoneSigned :: Domain -> FilePath -> Property NoInfo+forceZoneSigned :: Domain -> FilePath -> Property UnixLike forceZoneSigned domain zonefile = property ("zone signed for " ++ domain) $ liftIO $ do salt <- take 16 <$> saltSha1 let p = proc "dnssec-signzone"
src/Propellor/Property/Docker.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE FlexibleContexts, TypeSynonymInstances, FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts, TypeSynonymInstances, FlexibleInstances, TypeFamilies #-} -- | Docker support for propellor --@@ -48,8 +48,10 @@ import Propellor.Base hiding (init) import Propellor.Types.Docker import Propellor.Types.Container+import Propellor.Types.Core import Propellor.Types.CmdLine import Propellor.Types.Info+import Propellor.Container import qualified Propellor.Property.File as File import qualified Propellor.Property.Apt as Apt import qualified Propellor.Property.Cmd as Cmd@@ -66,16 +68,17 @@ import qualified Data.Map as M import System.Console.Concurrent -installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.installed ["docker.io"] -- | Configures docker with an authentication file, so that images can be -- pushed to index.docker.io. Optional.-configured :: Property HasInfo+configured :: Property (HasInfo + DebianLike) configured = prop `requires` installed where+ prop :: Property (HasInfo + DebianLike) prop = withPrivData src anyContext $ \getcfg ->- property "docker configured" $ getcfg $ \cfg -> ensureProperty $+ property' "docker configured" $ \w -> getcfg $ \cfg -> ensureProperty w $ "/root/.dockercfg" `File.hasContent` privDataLines cfg src = PrivDataSourceFileFromCommand DockerAuthentication "/root/.dockercfg" "docker login"@@ -88,6 +91,11 @@ -- | A docker container. data Container = Container Image Host +instance IsContainer Container where+ containerProperties (Container _ h) = containerProperties h+ containerInfo (Container _ h) = containerInfo h+ setContainerProperties (Container i h) ps = Container i (setContainerProperties h ps)+ class HasImage a where getImageName :: a -> Image @@ -97,22 +105,17 @@ instance HasImage Container where getImageName (Container i _) = i -instance PropAccum Container where- (Container i h) `addProp` p = Container i (h `addProp` p)- (Container i h) `addPropFront` p = Container i (h `addPropFront` p)- getProperties (Container _ h) = hostProperties h- -- | Defines a Container with a given name, image, and properties.--- Properties can be added to configure the Container.+-- Add properties to configure the Container. ----- > container "web-server" "debian"+-- > container "web-server" (latestImage "debian") $ props -- > & publish "80:80" -- > & Apt.installed {"apache2"] -- > & ...-container :: ContainerName -> Image -> Container-container cn image = Container image (Host cn [] info)+container :: ContainerName -> Image -> Props metatypes -> Container+container cn image (Props ps) = Container image (Host cn ps info) where- info = dockerInfo mempty+ info = dockerInfo mempty <> mconcat (map getInfoRecursive ps) -- | Ensures that a docker container is set up and running. --@@ -124,7 +127,7 @@ -- -- Reverting this property ensures that the container is stopped and -- removed.-docked :: Container -> RevertableProperty HasInfo+docked :: Container -> RevertableProperty (HasInfo + Linux) (HasInfo + Linux) docked ctr@(Container _ h) = (propagateContainerInfo ctr (go "docked" setup)) <!>@@ -132,11 +135,12 @@ where cn = hostName h - go desc a = property (desc ++ " " ++ cn) $ do+ go desc a = property' (desc ++ " " ++ cn) $ \w -> do hn <- asks hostName let cid = ContainerId hn cn- ensureProperties [a cid (mkContainerInfo cid ctr)]+ ensureProperty w $ a cid (mkContainerInfo cid ctr) + setup :: ContainerId -> ContainerInfo -> Property Linux setup cid (ContainerInfo image runparams) = provisionContainer cid `requires`@@ -144,8 +148,9 @@ `requires` installed + teardown :: ContainerId -> ContainerInfo -> Property Linux teardown cid (ContainerInfo image _runparams) =- combineProperties ("undocked " ++ fromContainerId cid)+ combineProperties ("undocked " ++ fromContainerId cid) $ toProps [ stoppedContainer cid , property ("cleaned up " ++ fromContainerId cid) $ liftIO $ report <$> mapM id@@ -155,32 +160,32 @@ ] -- | Build the image from a directory containing a Dockerfile.-imageBuilt :: HasImage c => FilePath -> c -> Property NoInfo-imageBuilt directory ctr = describe built msg+imageBuilt :: HasImage c => FilePath -> c -> Property Linux+imageBuilt directory ctr = built `describe` msg where msg = "docker image " ++ (imageIdentifier image) ++ " built from " ++ directory- built = Cmd.cmdProperty' dockercmd ["build", "--tag", imageIdentifier image, "./"] workDir- `assume` MadeChange+ built :: Property Linux+ built = tightenTargets $+ Cmd.cmdProperty' dockercmd ["build", "--tag", imageIdentifier image, "./"] workDir+ `assume` MadeChange workDir p = p { cwd = Just directory } image = getImageName ctr -- | Pull the image from the standard Docker Hub registry.-imagePulled :: HasImage c => c -> Property NoInfo-imagePulled ctr = describe pulled msg+imagePulled :: HasImage c => c -> Property Linux+imagePulled ctr = pulled `describe` msg where msg = "docker image " ++ (imageIdentifier image) ++ " pulled"- pulled = Cmd.cmdProperty dockercmd ["pull", imageIdentifier image]- `assume` MadeChange+ pulled :: Property Linux+ pulled = tightenTargets $ + Cmd.cmdProperty dockercmd ["pull", imageIdentifier image]+ `assume` MadeChange image = getImageName ctr -propagateContainerInfo :: (IsProp (Property i)) => Container -> Property i -> Property HasInfo-propagateContainerInfo ctr@(Container _ h) p = propagateContainer cn ctr p'+propagateContainerInfo :: Container -> Property (HasInfo + Linux) -> Property (HasInfo + Linux)+propagateContainerInfo ctr@(Container _ h) p = propagateContainer cn ctr $+ p `addInfoProperty` dockerinfo where- p' = infoProperty- (propertyDesc p)- (propertySatisfy p)- (propertyInfo p <> dockerinfo)- (propertyChildren p) dockerinfo = dockerInfo $ mempty { _dockerContainers = M.singleton cn h } cn = hostName h@@ -191,8 +196,8 @@ where runparams = map (\(DockerRunParam mkparam) -> mkparam hn) (_dockerRunParams info)- info = getInfo $ hostInfo h'- h' = h+ info = fromInfo $ hostInfo h'+ h' = setContainerProps h $ containerProps h -- Restart by default so container comes up on -- boot or when docker is upgraded. &^ restartAlways@@ -209,14 +214,15 @@ -- that were not set up using propellor. -- -- Generally, should come after the properties for the desired containers.-garbageCollected :: Property NoInfo-garbageCollected = propertyList "docker garbage collected"- [ gccontainers- , gcimages- ]+garbageCollected :: Property Linux+garbageCollected = propertyList "docker garbage collected" $ props+ & gccontainers+ & gcimages where+ gccontainers :: Property Linux gccontainers = property "docker containers garbage collected" $ liftIO $ report <$> (mapM removeContainer =<< listContainers AllContainers)+ gcimages :: Property Linux gcimages = property "docker images garbage collected" $ liftIO $ report <$> (mapM removeImage =<< listImages) @@ -225,8 +231,8 @@ -- Currently, this consists of making pam_loginuid lines optional in -- the pam config, to work around <https://github.com/docker/docker/issues/5663> -- which affects docker 1.2.0.-tweaked :: Property NoInfo-tweaked = cmdProperty "sh"+tweaked :: Property Linux+tweaked = tightenTargets $ cmdProperty "sh" [ "-c" , "sed -ri 's/^session\\s+required\\s+pam_loginuid.so$/session optional pam_loginuid.so/' /etc/pam.d/*" ]@@ -239,10 +245,11 @@ -- other GRUB_CMDLINE_LINUX_DEFAULT settings. -- -- Only takes effect after reboot. (Not automated.)-memoryLimited :: Property NoInfo-memoryLimited = "/etc/default/grub" `File.containsLine` cfg- `describe` "docker memory limited"- `onChange` (cmdProperty "update-grub" [] `assume` MadeChange)+memoryLimited :: Property DebianLike+memoryLimited = tightenTargets $+ "/etc/default/grub" `File.containsLine` cfg+ `describe` "docker memory limited"+ `onChange` (cmdProperty "update-grub" [] `assume` MadeChange) where cmdline = "cgroup_enable=memory swapaccount=1" cfg = "GRUB_CMDLINE_LINUX_DEFAULT=\""++cmdline++"\""@@ -300,15 +307,15 @@ imageIdentifier (ImageUID uid) = uid -- | Set custom dns server for container.-dns :: String -> Property HasInfo+dns :: String -> Property (HasInfo + Linux) dns = runProp "dns" -- | Set container host name.-hostname :: String -> Property HasInfo+hostname :: String -> Property (HasInfo + Linux) hostname = runProp "hostname" -- | Set name of container.-name :: String -> Property HasInfo+name :: String -> Property (HasInfo + Linux) name = runProp "name" class Publishable p where@@ -322,15 +329,15 @@ toPublish = id -- | Publish a container's port to the host-publish :: Publishable p => p -> Property HasInfo+publish :: Publishable p => p -> Property (HasInfo + Linux) publish = runProp "publish" . toPublish -- | Expose a container's port without publishing it.-expose :: String -> Property HasInfo+expose :: String -> Property (HasInfo + Linux) expose = runProp "expose" -- | Username or UID for container.-user :: String -> Property HasInfo+user :: String -> Property (HasInfo + Linux) user = runProp "user" class Mountable p where@@ -346,17 +353,17 @@ toMount = id -- | Mount a volume-volume :: Mountable v => v -> Property HasInfo+volume :: Mountable v => v -> Property (HasInfo + Linux) volume = runProp "volume" . toMount -- | Mount a volume from the specified container into the current -- container.-volumes_from :: ContainerName -> Property HasInfo+volumes_from :: ContainerName -> Property (HasInfo + Linux) volumes_from cn = genProp "volumes-from" $ \hn -> fromContainerId (ContainerId hn cn) -- | Work dir inside the container.-workdir :: String -> Property HasInfo+workdir :: String -> Property (HasInfo + Linux) workdir = runProp "workdir" -- | Memory limit for container.@@ -364,18 +371,18 @@ -- -- Note: Only takes effect when the host has the memoryLimited property -- enabled.-memory :: String -> Property HasInfo+memory :: String -> Property (HasInfo + Linux) memory = runProp "memory" -- | CPU shares (relative weight). -- -- By default, all containers run at the same priority, but you can tell -- the kernel to give more CPU time to a container using this property.-cpuShares :: Int -> Property HasInfo+cpuShares :: Int -> Property (HasInfo + Linux) cpuShares = runProp "cpu-shares" . show -- | Link with another container on the same host.-link :: ContainerName -> ContainerAlias -> Property HasInfo+link :: ContainerName -> ContainerAlias -> Property (HasInfo + Linux) link linkwith calias = genProp "link" $ \hn -> fromContainerId (ContainerId hn linkwith) ++ ":" ++ calias @@ -387,24 +394,24 @@ -- propellor; as well as keeping badly behaved containers running, -- it ensures that containers get started back up after reboot or -- after docker is upgraded.-restartAlways :: Property HasInfo+restartAlways :: Property (HasInfo + Linux) restartAlways = runProp "restart" "always" -- | Docker will restart the container if it exits nonzero. -- If a number is provided, it will be restarted only up to that many -- times.-restartOnFailure :: Maybe Int -> Property HasInfo+restartOnFailure :: Maybe Int -> Property (HasInfo + Linux) restartOnFailure Nothing = runProp "restart" "on-failure" restartOnFailure (Just n) = runProp "restart" ("on-failure:" ++ show n) -- | Makes docker not restart a container when it exits -- Note that this includes not restarting it on boot!-restartNever :: Property HasInfo+restartNever :: Property (HasInfo + Linux) restartNever = runProp "restart" "no" -- | Set environment variable with a tuple composed by the environment -- variable name and its value.-environment :: (String, String) -> Property HasInfo+environment :: (String, String) -> Property (HasInfo + Linux) environment (k, v) = runProp "env" $ k ++ "=" ++ v -- | A container is identified by its name, and the host@@ -441,9 +448,9 @@ containerDesc :: (IsProp (Property i)) => ContainerId -> Property i -> Property i containerDesc cid p = p `describe` desc where- desc = "container " ++ fromContainerId cid ++ " " ++ propertyDesc p+ desc = "container " ++ fromContainerId cid ++ " " ++ getDesc p -runningContainer :: ContainerId -> Image -> [RunParam] -> Property NoInfo+runningContainer :: ContainerId -> Image -> [RunParam] -> Property Linux runningContainer cid@(ContainerId hn cn) image runps = containerDesc cid $ property "running" $ do l <- liftIO $ listContainers RunningContainers if cid `elem` l@@ -507,6 +514,7 @@ retry (n-1) a _ -> return v + go :: ImageIdentifier i => i -> Propellor Result go img = liftIO $ do clearProvisionedFlag cid createDirectoryIfMissing True (takeDirectory $ identFile cid)@@ -558,7 +566,7 @@ -- | Once a container is running, propellor can be run inside -- it to provision it.-provisionContainer :: ContainerId -> Property NoInfo+provisionContainer :: ContainerId -> Property Linux provisionContainer cid = containerDesc cid $ property "provisioned" $ liftIO $ do let shim = Shim.file (localdir </> "propellor") (localdir </> shimdir cid) let params = ["--continue", show $ toChain cid]@@ -580,16 +588,14 @@ Nothing -> errorMessage "bad container id" Just cid -> case findHostNoAlias hostlist hn of Nothing -> errorMessage ("cannot find host " ++ hn)- Just parenthost -> case M.lookup (containerName cid) (_dockerContainers $ getInfo $ hostInfo parenthost) of+ Just parenthost -> case M.lookup (containerName cid) (_dockerContainers $ fromInfo $ hostInfo parenthost) of Nothing -> errorMessage ("cannot find container " ++ containerName cid ++ " docked on host " ++ hn) Just h -> go cid h where go cid h = do changeWorkingDirectory localdir onlyProcess (provisioningLock cid) $ do- r <- runPropellor h $ ensureProperties $- map ignoreInfo $- hostProperties h+ r <- runPropellor h $ ensureChildProperties $ hostProperties h flushConcurrentOutput putStrLn $ "\n" ++ show r @@ -599,15 +605,16 @@ startContainer :: ContainerId -> IO Bool startContainer cid = boolSystem dockercmd [Param "start", Param $ fromContainerId cid ] -stoppedContainer :: ContainerId -> Property NoInfo-stoppedContainer cid = containerDesc cid $ property desc $+stoppedContainer :: ContainerId -> Property Linux+stoppedContainer cid = containerDesc cid $ property' desc $ \w -> ifM (liftIO $ elem cid <$> listContainers RunningContainers)- ( liftIO cleanup `after` ensureProperty- (property desc $ liftIO $ toResult <$> stopContainer cid)+ ( liftIO cleanup `after` ensureProperty w stop , return NoChange ) where desc = "stopped"+ stop :: Property Linux+ stop = property desc $ liftIO $ toResult <$> stopContainer cid cleanup = do nukeFile $ identFile cid removeDirectoryRecursive $ shimdir cid@@ -651,14 +658,14 @@ listImages :: IO [ImageUID] listImages = map ImageUID . lines <$> readProcess dockercmd ["images", "--all", "--quiet"] -runProp :: String -> RunParam -> Property HasInfo-runProp field val = pureInfoProperty (param) $+runProp :: String -> RunParam -> Property (HasInfo + Linux)+runProp field val = tightenTargets $ pureInfoProperty (param) $ mempty { _dockerRunParams = [DockerRunParam (\_ -> "--"++param)] } where param = field++"="++val -genProp :: String -> (HostName -> RunParam) -> Property HasInfo-genProp field mkval = pureInfoProperty field $+genProp :: String -> (HostName -> RunParam) -> Property (HasInfo + Linux)+genProp field mkval = tightenTargets $ pureInfoProperty field $ mempty { _dockerRunParams = [DockerRunParam (\hn -> "--"++field++"=" ++ mkval hn)] } dockerInfo :: DockerInfo -> Info
src/Propellor/Property/Fail2Ban.hs view
@@ -5,24 +5,24 @@ import qualified Propellor.Property.Service as Service import Propellor.Property.ConfFile -installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.serviceInstalledRunning "fail2ban" -reloaded :: Property NoInfo+reloaded :: Property DebianLike reloaded = Service.reloaded "fail2ban" type Jail = String -- | By default, fail2ban only enables the ssh jail, but many others -- are available to be enabled, for example "postfix-sasl"-jailEnabled :: Jail -> Property NoInfo+jailEnabled :: Jail -> Property DebianLike jailEnabled name = jailConfigured name "enabled" "true" `onChange` reloaded -- | Configures a jail. For example: -- -- > jailConfigured "sshd" "port" "2222"-jailConfigured :: Jail -> IniKey -> String -> Property NoInfo+jailConfigured :: Jail -> IniKey -> String -> Property UnixLike jailConfigured name key value = jailConfFile name `containsIniSetting` (name, key, value)
src/Propellor/Property/File.hs view
@@ -9,14 +9,14 @@ type Line = String -- | Replaces all the content of a file.-hasContent :: FilePath -> [Line] -> Property NoInfo+hasContent :: FilePath -> [Line] -> Property UnixLike f `hasContent` newcontent = fileProperty ("replace " ++ f) (\_oldcontent -> newcontent) f -- | Replaces all the content of a file, ensuring that its modes do not -- allow it to be read or written by anyone other than the current user-hasContentProtected :: FilePath -> [Line] -> Property NoInfo+hasContentProtected :: FilePath -> [Line] -> Property UnixLike f `hasContentProtected` newcontent = fileProperty' writeFileProtected ("replace " ++ f) (\_oldcontent -> newcontent) f@@ -25,38 +25,38 @@ -- -- The file's permissions are preserved if the file already existed. -- Otherwise, they're set to 600.-hasPrivContent :: IsContext c => FilePath -> c -> Property HasInfo+hasPrivContent :: IsContext c => FilePath -> c -> Property (HasInfo + UnixLike) hasPrivContent f = hasPrivContentFrom (PrivDataSourceFile (PrivFile f) f) f -- | Like hasPrivContent, but allows specifying a source -- for PrivData, rather than using PrivDataSourceFile .-hasPrivContentFrom :: (IsContext c, IsPrivDataSource s) => s -> FilePath -> c -> Property HasInfo+hasPrivContentFrom :: (IsContext c, IsPrivDataSource s) => s -> FilePath -> c -> Property (HasInfo + UnixLike) hasPrivContentFrom = hasPrivContent' writeFileProtected -- | Leaves the file at its default or current mode, -- allowing "private" data to be read. -- -- Use with caution!-hasPrivContentExposed :: IsContext c => FilePath -> c -> Property HasInfo+hasPrivContentExposed :: IsContext c => FilePath -> c -> Property (HasInfo + UnixLike) hasPrivContentExposed f = hasPrivContentExposedFrom (PrivDataSourceFile (PrivFile f) f) f -hasPrivContentExposedFrom :: (IsContext c, IsPrivDataSource s) => s -> FilePath -> c -> Property HasInfo+hasPrivContentExposedFrom :: (IsContext c, IsPrivDataSource s) => s -> FilePath -> c -> Property (HasInfo + UnixLike) hasPrivContentExposedFrom = hasPrivContent' writeFile -hasPrivContent' :: (IsContext c, IsPrivDataSource s) => (FilePath -> String -> IO ()) -> s -> FilePath -> c -> Property HasInfo+hasPrivContent' :: (IsContext c, IsPrivDataSource s) => (FilePath -> String -> IO ()) -> s -> FilePath -> c -> Property (HasInfo + UnixLike) hasPrivContent' writer source f context = withPrivData source context $ \getcontent -> - property desc $ getcontent $ \privcontent -> - ensureProperty $ fileProperty' writer desc+ property' desc $ \o -> getcontent $ \privcontent -> + ensureProperty o $ fileProperty' writer desc (\_oldcontent -> privDataLines privcontent) f where desc = "privcontent " ++ f -- | Ensures that a line is present in a file, adding it to the end if not.-containsLine :: FilePath -> Line -> Property NoInfo+containsLine :: FilePath -> Line -> Property UnixLike f `containsLine` l = f `containsLines` [l] -containsLines :: FilePath -> [Line] -> Property NoInfo+containsLines :: FilePath -> [Line] -> Property UnixLike f `containsLines` ls = fileProperty (f ++ " contains:" ++ show ls) go f where go content = content ++ filter (`notElem` content) ls@@ -64,27 +64,28 @@ -- | Ensures that a line is not present in a file. -- Note that the file is ensured to exist, so if it doesn't, an empty -- file will be written.-lacksLine :: FilePath -> Line -> Property NoInfo+lacksLine :: FilePath -> Line -> Property UnixLike f `lacksLine` l = fileProperty (f ++ " remove: " ++ l) (filter (/= l)) f -lacksLines :: FilePath -> [Line] -> Property NoInfo+lacksLines :: FilePath -> [Line] -> Property UnixLike 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')+basedOn :: FilePath -> (FilePath, [Line] -> [Line]) -> Property UnixLike+f `basedOn` (f', a) = property' desc $ \o -> do+ tmpl <- liftIO $ readFile f'+ ensureProperty o $ fileProperty desc (\_ -> a $ lines $ tmpl) 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 :: FilePath -> Property UnixLike notPresent f = check (doesFileExist f) $ property (f ++ " not present") $ makeChange $ nukeFile f -fileProperty :: Desc -> ([Line] -> [Line]) -> FilePath -> Property NoInfo+fileProperty :: Desc -> ([Line] -> [Line]) -> FilePath -> Property UnixLike fileProperty = fileProperty' writeFile-fileProperty' :: (FilePath -> String -> IO ()) -> Desc -> ([Line] -> [Line]) -> FilePath -> Property NoInfo+fileProperty' :: (FilePath -> String -> IO ()) -> Desc -> ([Line] -> [Line]) -> FilePath -> Property UnixLike fileProperty' writer desc a f = property desc $ go =<< liftIO (doesFileExist f) where go True = do@@ -103,7 +104,7 @@ setOwnerAndGroup f' (fileOwner s) (fileGroup s) -- | Ensures a directory exists.-dirExists :: FilePath -> Property NoInfo+dirExists :: FilePath -> Property UnixLike dirExists d = check (not <$> doesDirectoryExist d) $ property (d ++ " exists") $ makeChange $ createDirectoryIfMissing True d @@ -113,7 +114,7 @@ -- | Creates or atomically updates a symbolic link. -- -- Does not overwrite regular files or directories.-isSymlinkedTo :: FilePath -> LinkTarget -> Property NoInfo+isSymlinkedTo :: FilePath -> LinkTarget -> Property UnixLike link `isSymlinkedTo` (LinkTarget target) = property desc $ go =<< (liftIO $ tryIO $ getSymbolicLinkStatus link) where@@ -135,7 +136,7 @@ updateLink = createSymbolicLink target `viaStableTmp` link -- | Ensures that a file is a copy of another (regular) file.-isCopyOf :: FilePath -> FilePath -> Property NoInfo+isCopyOf :: FilePath -> FilePath -> Property UnixLike f `isCopyOf` f' = property desc $ go =<< (liftIO $ tryIO $ getFileStatus f') where desc = f ++ " is copy of " ++ f'@@ -156,7 +157,7 @@ [Param "--preserve=all", Param "--", File src, File dest] -- | Ensures that a file/dir has the specified owner and group.-ownerGroup :: FilePath -> User -> Group -> Property NoInfo+ownerGroup :: FilePath -> User -> Group -> Property UnixLike ownerGroup f (User owner) (Group group) = p `describe` (f ++ " owner " ++ og) where p = cmdProperty "chown" [og, f]@@ -164,7 +165,7 @@ og = owner ++ ":" ++ group -- | Ensures that a file/dir has the specfied mode.-mode :: FilePath -> FileMode -> Property NoInfo+mode :: FilePath -> FileMode -> Property UnixLike mode f v = p `changesFile` f where p = property (f ++ " mode " ++ show v) $ do
src/Propellor/Property/Firewall.hs view
@@ -26,10 +26,10 @@ import qualified Propellor.Property.Apt as Apt import qualified Propellor.Property.Network as Network -installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.installed ["iptables"] -rule :: Chain -> Table -> Target -> Rules -> Property NoInfo+rule :: Chain -> Table -> Target -> Rules -> Property Linux rule c tb tg rs = property ("firewall rule: " <> show r) addIpTable where r = Rule c tb tg rs
src/Propellor/Property/FreeBSD/Pkg.hs view
@@ -22,8 +22,8 @@ in lines <$> readProcess p a -pkgCmdProperty :: String -> [String] -> UncheckedProperty NoInfo-pkgCmdProperty cmd args =+pkgCmdProperty :: String -> [String] -> UncheckedProperty FreeBSD+pkgCmdProperty cmd args = tightenTargets $ let (p, a) = pkgCommand cmd args in@@ -44,13 +44,14 @@ pkgUpdated :: PkgUpdate -> Bool pkgUpdated (PkgUpdate _) = True -update :: Property HasInfo+update :: Property (HasInfo + FreeBSD) update = let upd = pkgCmd "update" [] go = ifM (pkgUpdated <$> askInfo) ((noChange), (liftIO upd >> return MadeChange)) in- infoProperty "pkg update has run" go (addInfo mempty (PkgUpdate "")) []+ (property "pkg update has run" go :: Property FreeBSD)+ `setInfoProperty` (toInfo (PkgUpdate "")) newtype PkgUpgrade = PkgUpgrade String deriving (Typeable, Monoid, Show)@@ -60,17 +61,19 @@ pkgUpgraded :: PkgUpgrade -> Bool pkgUpgraded (PkgUpgrade _) = True -upgrade :: Property HasInfo+upgrade :: Property (HasInfo + FreeBSD) upgrade = let upd = pkgCmd "upgrade" [] go = ifM (pkgUpgraded <$> askInfo) ((noChange), (liftIO upd >> return MadeChange)) in- infoProperty "pkg upgrade has run" go (addInfo mempty (PkgUpgrade "")) [] `requires` update+ (property "pkg upgrade has run" go :: Property FreeBSD)+ `setInfoProperty` (toInfo (PkgUpdate ""))+ `requires` update type Package = String -installed :: Package -> Property NoInfo+installed :: Package -> Property FreeBSD installed pkg = check (isInstallable pkg) $ pkgCmdProperty "install" [pkg] isInstallable :: Package -> IO Bool
src/Propellor/Property/FreeBSD/Poudriere.hs view
@@ -26,20 +26,23 @@ poudriereConfigured :: PoudriereConfigured -> Bool poudriereConfigured (PoudriereConfigured _) = True -setConfigured :: Property HasInfo-setConfigured = pureInfoProperty "Poudriere Configured" (PoudriereConfigured "")+setConfigured :: Property (HasInfo + FreeBSD)+setConfigured = tightenTargets $ + pureInfoProperty "Poudriere Configured" (PoudriereConfigured "") -poudriere :: Poudriere -> Property HasInfo+poudriere :: Poudriere -> Property (HasInfo + FreeBSD) poudriere conf@(Poudriere _ _ _ _ _ _ zfs) = prop `requires` Pkg.installed "poudriere" `before` setConfigured where- confProp = File.containsLines poudriereConfigPath (toLines conf)+ confProp :: Property FreeBSD+ confProp = tightenTargets $+ File.containsLines poudriereConfigPath (toLines conf) setZfs (PoudriereZFS z p) = ZFS.zfsSetProperties z p `describe` "Configuring Poudriere with ZFS"- prop :: CombinedType (Property NoInfo) (Property NoInfo)+ prop :: Property FreeBSD prop | isJust zfs = ((setZfs $ fromJust zfs) `before` confProp)- | otherwise = propertyList "Configuring Poudriere without ZFS" [confProp]+ | otherwise = confProp `describe` "Configuring Poudriere without ZFS" poudriereCommand :: String -> [String] -> (String, [String]) poudriereCommand cmd args = ("poudriere", cmd:args)@@ -58,8 +61,8 @@ jailExists :: Jail -> IO Bool jailExists (Jail name _ _) = isInfixOf [name] <$> listJails -jail :: Jail -> Property NoInfo-jail j@(Jail name version arch) =+jail :: Jail -> Property FreeBSD+jail j@(Jail name version arch) = tightenTargets $ let chk = do c <- poudriereConfigured <$> askInfo@@ -70,7 +73,7 @@ createJail = cmdProperty cmd args in check chk createJail- `describe` unwords ["Create poudriere jail", name]+ `describe` unwords ["Create poudriere jail", name] data JailInfo = JailInfo String
src/Propellor/Property/Git.hs view
@@ -11,7 +11,7 @@ -- using git-daemon, run from inetd. -- -- Note that reverting this property does not remove or stop inetd.-daemonRunning :: FilePath -> RevertableProperty NoInfo+daemonRunning :: FilePath -> RevertableProperty DebianLike DebianLike daemonRunning exportdir = setup <!> unsetup where setup = containsLine conf (mkl "tcp4")@@ -47,7 +47,7 @@ , exportdir ] -installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.installed ["git"] type RepoUrl = String@@ -61,8 +61,8 @@ -- it will be recursively deleted first. -- -- A branch can be specified, to check out.-cloned :: User -> RepoUrl -> FilePath -> Maybe Branch -> Property NoInfo-cloned owner url dir mbranch = check originurl (property desc checkout)+cloned :: User -> RepoUrl -> FilePath -> Maybe Branch -> Property DebianLike+cloned owner url dir mbranch = check originurl go `requires` installed where desc = "git cloned " ++ url ++ " to " ++ dir@@ -74,12 +74,13 @@ return (v /= Just url) , return True )- checkout = do+ go :: Property DebianLike+ go = property' desc $ \w -> do liftIO $ do whenM (doesDirectoryExist dir) $ removeDirectoryRecursive dir createDirectoryIfMissing True (takeDirectory dir)- ensureProperty $ userScriptProperty owner (catMaybes checkoutcmds)+ ensureProperty w $ userScriptProperty owner (catMaybes checkoutcmds) `assume` MadeChange checkoutcmds = -- The </dev/null fixes an intermittent@@ -99,8 +100,8 @@ data GitShared = Shared Group | SharedAll | NotShared -bareRepo :: FilePath -> User -> GitShared -> Property NoInfo-bareRepo repo user gitshared = check (isRepo repo) $ propertyList ("git repo: " ++ repo) $+bareRepo :: FilePath -> User -> GitShared -> Property UnixLike+bareRepo repo user gitshared = check (isRepo repo) $ propertyList ("git repo: " ++ repo) $ toProps $ dirExists repo : case gitshared of NotShared -> [ ownerGroup repo user (userGroup user)@@ -121,7 +122,7 @@ isRepo repo' = isNothing <$> catchMaybeIO (readProcess "git" ["rev-parse", "--resolve-git-dir", repo']) -- | Set a key value pair in a git repo's configuration.-repoConfigured :: FilePath -> (String, String) -> Property NoInfo+repoConfigured :: FilePath -> (String, String) -> Property UnixLike repo `repoConfigured` (key, value) = check (not <$> alreadyconfigured) $ userScriptProperty (User "root") [ "cd " ++ repo@@ -141,7 +142,7 @@ lines <$> readProcess "git" ["-C", repo, "config", key] -- | Whether a repo accepts non-fast-forward pushes.-repoAcceptsNonFFs :: FilePath -> RevertableProperty NoInfo+repoAcceptsNonFFs :: FilePath -> RevertableProperty UnixLike UnixLike repoAcceptsNonFFs repo = accepts <!> refuses where accepts = repoConfigured repo ("receive.denyNonFastForwards", "false")@@ -152,7 +153,7 @@ -- | Sets a bare repository's default branch, which will be checked out -- when cloning it.-bareRepoDefaultBranch :: FilePath -> String -> Property NoInfo+bareRepoDefaultBranch :: FilePath -> String -> Property UnixLike bareRepoDefaultBranch repo branch = userScriptProperty (User "root") [ "cd " ++ repo
src/Propellor/Property/Gpg.hs view
@@ -6,7 +6,7 @@ import System.PosixCompat -installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.installed ["gnupg"] -- A numeric id, or a description of the key, in a form understood by gpg.@@ -22,11 +22,12 @@ -- -- Recommend only using this for low-value dedicated role keys. -- No attempt has been made to scrub the key out of memory once it's used.-keyImported :: GpgKeyId -> User -> Property HasInfo+keyImported :: GpgKeyId -> User -> Property (HasInfo + DebianLike) keyImported key@(GpgKeyId keyid) user@(User u) = prop `requires` installed where desc = u ++ " has gpg key " ++ show keyid+ prop :: Property (HasInfo + DebianLike) prop = withPrivData src (Context keyid) $ \getkey -> property desc $ getkey $ \key' -> do let keylines = privDataLines key'
src/Propellor/Property/Group.hs view
@@ -4,7 +4,7 @@ type GID = Int -exists :: Group -> Maybe GID -> Property NoInfo+exists :: Group -> Maybe GID -> Property UnixLike exists (Group group') mgid = check test (cmdProperty "addgroup" (args mgid)) `describe` unwords ["group", group'] where
src/Propellor/Property/Grub.hs view
@@ -19,20 +19,23 @@ -- bootloader. -- -- This includes running update-grub.-installed :: BIOS -> Property NoInfo+installed :: BIOS -> Property DebianLike installed bios = installed' bios `onChange` 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" []+mkConfig :: Property DebianLike+mkConfig = tightenTargets $ cmdProperty "update-grub" [] `assume` MadeChange -- | Installs grub; does not run update-grub.-installed' :: BIOS -> Property NoInfo-installed' bios = Apt.installed [pkg] `describe` "grub package installed"+installed' :: BIOS -> Property Linux+installed' bios = (aptinstall `pickOS` unsupportedOS)+ `describe` "grub package installed" where- pkg = case bios of+ aptinstall :: Property DebianLike+ aptinstall = Apt.installed [debpkg]+ debpkg = case bios of PC -> "grub-pc" EFI64 -> "grub-efi-amd64" EFI32 -> "grub-efi-ia32"@@ -48,8 +51,8 @@ -- on the device; it always does the work to reinstall it. It's a good idea -- to arrange for this property to only run once, by eg making it be run -- onChange after OS.cleanInstallOnce.-boots :: OSDevice -> Property NoInfo-boots dev = cmdProperty "grub-install" [dev]+boots :: OSDevice -> Property Linux+boots dev = tightenTargets $ cmdProperty "grub-install" [dev] `assume` MadeChange `describe` ("grub boots " ++ dev) @@ -61,10 +64,10 @@ -- -- The rootdev should be in the form "hd0", while the bootdev is in the form -- "xen/xvda".-chainPVGrub :: GrubDevice -> GrubDevice -> TimeoutSecs -> Property NoInfo-chainPVGrub rootdev bootdev timeout = combineProperties desc- [ File.dirExists "/boot/grub"- , "/boot/grub/menu.lst" `File.hasContent`+chainPVGrub :: GrubDevice -> GrubDevice -> TimeoutSecs -> Property DebianLike+chainPVGrub rootdev bootdev timeout = combineProperties desc $ props+ & File.dirExists "/boot/grub"+ & "/boot/grub/menu.lst" `File.hasContent` [ "default 1" , "timeout " ++ show timeout , ""@@ -73,12 +76,12 @@ , "kernel /boot/xen-shim" , "boot" ]- , "/boot/load.cf" `File.hasContent`+ & "/boot/load.cf" `File.hasContent` [ "configfile (" ++ bootdev ++ ")/boot/grub/grub.cfg" ]- , installed Xen- , flip flagFile "/boot/xen-shim" $ scriptProperty ["grub-mkimage --prefix '(" ++ bootdev ++ ")/boot/grub' -c /boot/load.cf -O x86_64-xen /usr/lib/grub/x86_64-xen/*.mod > /boot/xen-shim"]- `assume` MadeChange- `describe` "/boot-xen-shim"- ]+ & installed Xen+ & flip flagFile "/boot/xen-shim" xenshim where desc = "chain PV-grub"+ xenshim = scriptProperty ["grub-mkimage --prefix '(" ++ bootdev ++ ")/boot/grub' -c /boot/load.cf -O x86_64-xen /usr/lib/grub/x86_64-xen/*.mod > /boot/xen-shim"]+ `assume` MadeChange+ `describe` "/boot-xen-shim"
src/Propellor/Property/HostingProvider/CloudAtCost.hs view
@@ -6,19 +6,24 @@ import qualified Propellor.Property.User as User -- Clean up a system as installed by cloudatcost.com-decruft :: Property NoInfo-decruft = propertyList "cloudatcost cleanup"- [ Hostname.sane- , "worked around grub/lvm boot bug #743126" ==>+decruft :: Property DebianLike+decruft = propertyList "cloudatcost cleanup" $ props+ & Hostname.sane+ & grubbugfix+ & nukecruft+ where+ grubbugfix :: Property DebianLike+ grubbugfix = tightenTargets $ "/etc/default/grub" `File.containsLine` "GRUB_DISABLE_LINUX_UUID=true"- `onChange` (cmdProperty "update-grub" [] `assume` MadeChange)- `onChange` (cmdProperty "update-initramfs" ["-u"] `assume` MadeChange)- , combineProperties "nuked cloudatcost cruft"- [ File.notPresent "/etc/rc.local"- , File.notPresent "/etc/init.d/S97-setup.sh"- , File.notPresent "/zang-debian.sh"- , File.notPresent "/bin/npasswd"- , User.nuked (User "user") User.YesReallyDeleteHome- ]- ]+ `describe` "worked around grub/lvm boot bug #743126"+ `onChange` (cmdProperty "update-grub" [] `assume` MadeChange)+ `onChange` (cmdProperty "update-initramfs" ["-u"] `assume` MadeChange)+ nukecruft :: Property Linux+ nukecruft = tightenTargets $+ combineProperties "nuked cloudatcost cruft" $ props+ & File.notPresent "/etc/rc.local"+ & File.notPresent "/etc/init.d/S97-setup.sh"+ & File.notPresent "/zang-debian.sh"+ & File.notPresent "/bin/npasswd"+ & User.nuked (User "user") User.YesReallyDeleteHome
src/Propellor/Property/HostingProvider/DigitalOcean.hs view
@@ -18,16 +18,15 @@ -- If the power is cycled, the non-distro kernel still boots up. -- So, this property also checks if the running kernel is present in /boot, -- and if not, reboots immediately into a distro kernel.-distroKernel :: Property NoInfo-distroKernel = propertyList "digital ocean distro kernel hack"- [ Apt.installed ["grub-pc", "kexec-tools", "file"]- , "/etc/default/kexec" `File.containsLines`+distroKernel :: Property DebianLike+distroKernel = propertyList "digital ocean distro kernel hack" $ props+ & Apt.installed ["grub-pc", "kexec-tools", "file"]+ & "/etc/default/kexec" `File.containsLines` [ "LOAD_KEXEC=true" , "USE_GRUB_CONFIG=true" ] `describe` "kexec configured"- , check (not <$> runningInstalledKernel) Reboot.now+ & check (not <$> runningInstalledKernel) Reboot.now `describe` "running installed kernel"- ] runningInstalledKernel :: IO Bool runningInstalledKernel = do
src/Propellor/Property/HostingProvider/Linode.hs view
@@ -8,12 +8,13 @@ -- | Linode's pv-grub-x86_64 does not currently support booting recent -- Debian kernels compressed with xz. This sets up pv-grub chaining to enable -- it.-chainPVGrub :: Grub.TimeoutSecs -> Property NoInfo+chainPVGrub :: Grub.TimeoutSecs -> Property DebianLike chainPVGrub = Grub.chainPVGrub "hd0" "xen/xvda" -- | Linode disables mlocate's cron job's execute permissions, -- presumably to avoid disk IO. This ensures it's executable.-mlocateEnabled :: Property NoInfo-mlocateEnabled = "/etc/cron.daily/mlocate"- `File.mode` combineModes (readModes ++ executeModes)+mlocateEnabled :: Property DebianLike+mlocateEnabled = tightenTargets $+ "/etc/cron.daily/mlocate"+ `File.mode` combineModes (readModes ++ executeModes)
src/Propellor/Property/Hostname.hs view
@@ -22,20 +22,20 @@ -- Also, the </etc/hosts> 127.0.0.1 line is set to localhost. Putting any -- other hostnames there is not best practices and can lead to annoying -- messages from eg, apache.-sane :: Property NoInfo+sane :: Property UnixLike sane = sane' extractDomain -sane' :: ExtractDomain -> Property NoInfo-sane' extractdomain = property ("sane hostname") $- ensureProperty . setTo' extractdomain =<< asks hostName+sane' :: ExtractDomain -> Property UnixLike+sane' extractdomain = property' ("sane hostname") $ \w ->+ ensureProperty w . 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 :: HostName -> Property UnixLike setTo = setTo' extractDomain -setTo' :: ExtractDomain -> HostName -> Property NoInfo-setTo' extractdomain hn = combineProperties desc+setTo' :: ExtractDomain -> HostName -> Property UnixLike+setTo' extractdomain hn = combineProperties desc $ toProps [ "/etc/hostname" `File.hasContent` [basehost] , hostslines $ catMaybes [ if null domain@@ -65,11 +65,12 @@ -- | Makes </etc/resolv.conf> contain search and domain lines for -- the domain that the hostname is in.-searchDomain :: Property NoInfo+searchDomain :: Property UnixLike searchDomain = searchDomain' extractDomain -searchDomain' :: ExtractDomain -> Property NoInfo-searchDomain' extractdomain = property desc (ensureProperty . go =<< asks hostName)+searchDomain' :: ExtractDomain -> Property UnixLike+searchDomain' extractdomain = property' desc $ \w ->+ (ensureProperty w . go =<< asks hostName) where desc = "resolv.conf search and domain configured" go hn =
src/Propellor/Property/Journald.hs view
@@ -5,7 +5,7 @@ import Utility.DataUnits -- | Configures journald, restarting it so the changes take effect.-configured :: Systemd.Option -> String -> Property NoInfo+configured :: Systemd.Option -> String -> Property Linux configured option value = Systemd.configured "/etc/systemd/journald.conf" option value `onChange` Systemd.restarted "systemd-journald"@@ -14,28 +14,28 @@ -- Examples: "100 megabytes" or "0.5tb" type DataSize = String -configuredSize :: Systemd.Option -> DataSize -> Property NoInfo+configuredSize :: Systemd.Option -> DataSize -> Property Linux configuredSize option s = case readSize dataUnits s of Just sz -> configured option (systemdSizeUnits sz) Nothing -> property ("unable to parse " ++ option ++ " data size " ++ s) $ return FailedChange -systemMaxUse :: DataSize -> Property NoInfo+systemMaxUse :: DataSize -> Property Linux systemMaxUse = configuredSize "SystemMaxUse" -runtimeMaxUse :: DataSize -> Property NoInfo+runtimeMaxUse :: DataSize -> Property Linux runtimeMaxUse = configuredSize "RuntimeMaxUse" -systemKeepFree :: DataSize -> Property NoInfo+systemKeepFree :: DataSize -> Property Linux systemKeepFree = configuredSize "SystemKeepFree" -runtimeKeepFree :: DataSize -> Property NoInfo+runtimeKeepFree :: DataSize -> Property Linux runtimeKeepFree = configuredSize "RuntimeKeepFree" -systemMaxFileSize :: DataSize -> Property NoInfo+systemMaxFileSize :: DataSize -> Property Linux systemMaxFileSize = configuredSize "SystemMaxFileSize" -runtimeMaxFileSize :: DataSize -> Property NoInfo+runtimeMaxFileSize :: DataSize -> Property Linux runtimeMaxFileSize = configuredSize "RuntimeMaxFileSize" -- Generates size units as used in journald.conf.
src/Propellor/Property/Kerberos.hs view
@@ -34,25 +34,25 @@ principal :: String -> Maybe String -> Maybe Realm -> Principal principal p i r = p ++ maybe "" ("/"++) i ++ maybe "" ("@" ++) r -installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.installed ["krb5-user"] -kdcInstalled :: Property NoInfo+kdcInstalled :: Property DebianLike kdcInstalled = Apt.serviceInstalledRunning "krb5-kdc" -adminServerInstalled :: Property NoInfo+adminServerInstalled :: Property DebianLike adminServerInstalled = Apt.serviceInstalledRunning "krb5-admin-server" -kpropServerInstalled :: Property HasInfo+kpropServerInstalled :: Property DebianLike kpropServerInstalled = propertyList "kprop server installed" $ props & kdcInstalled & Apt.installed ["openbsd-inetd"] & "/etc/inetd.conf" `File.containsLines`- [ "krb5_prop\tstream\ttcp\tnowait\troot\t/usr/sbin/kpropd kpropd"- , "krb5_prop\tstream\ttcp6\tnowait\troot\t/usr/sbin/kpropd kpropd"- ]+ [ "krb5_prop\tstream\ttcp\tnowait\troot\t/usr/sbin/kpropd kpropd"+ , "krb5_prop\tstream\ttcp6\tnowait\troot\t/usr/sbin/kpropd kpropd"+ ] -kpropAcls :: [String] -> Property NoInfo+kpropAcls :: [String] -> Property UnixLike kpropAcls ps = kpropdAclPath `File.hasContent` ps `describe` "kprop server ACLs" k5srvutil :: (Maybe FilePath) -> [String] -> IO String@@ -82,13 +82,14 @@ h <- homedir user return $ h </> ".k5login" -k5login :: User -> [Principal] -> Property NoInfo-k5login user@(User u) ps = property (u ++ " has k5login") $ do+k5login :: User -> [Principal] -> Property UnixLike+k5login user@(User u) ps = property' desc $ \w -> do f <- liftIO $ k5loginPath user liftIO $ do createDirectoryIfMissing True (takeDirectory f) writeFile f (unlines ps)- ensureProperties- [ File.ownerGroup f user (userGroup user)- , File.ownerGroup (takeDirectory f) user (userGroup user)- ]+ ensureProperty w $ combineProperties desc $ props+ & File.ownerGroup f user (userGroup user)+ & File.ownerGroup (takeDirectory f) user (userGroup user)+ where+ desc = u ++ " has k5login"
src/Propellor/Property/LetsEncrypt.hs view
@@ -7,7 +7,7 @@ import System.Posix.Files -installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.installed ["letsencrypt"] -- | Tell the letsencrypt client that you agree with the Let's Encrypt@@ -39,15 +39,16 @@ -- -- See `Propellor.Property.Apache.httpsVirtualHost` for a more complete -- integration of apache with letsencrypt, that's built on top of this.-letsEncrypt :: AgreeTOS -> Domain -> WebRoot -> Property NoInfo+letsEncrypt :: AgreeTOS -> Domain -> WebRoot -> Property DebianLike letsEncrypt tos domain = letsEncrypt' tos domain [] -- | Like `letsEncrypt`, but the certificate can be obtained for multiple -- domains.-letsEncrypt' :: AgreeTOS -> Domain -> [Domain] -> WebRoot -> Property NoInfo+letsEncrypt' :: AgreeTOS -> Domain -> [Domain] -> WebRoot -> Property DebianLike letsEncrypt' (AgreeTOS memail) domain domains webroot = prop `requires` installed where+ prop :: Property UnixLike prop = property desc $ do startstats <- liftIO getstats (transcript, ok) <- liftIO $
src/Propellor/Property/LightDM.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE FlexibleInstances #-}- -- | Maintainer: Sean Whitton <spwhitton@spwhitton.name> module Propellor.Property.LightDM where@@ -8,11 +6,11 @@ import qualified Propellor.Property.Apt as Apt import qualified Propellor.Property.ConfFile as ConfFile -installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.installed ["lightdm"] -- | Configures LightDM to skip the login screen and autologin as a user.-autoLogin :: User -> Property NoInfo+autoLogin :: User -> Property UnixLike autoLogin (User u) = "/etc/lightdm/lightdm.conf" `ConfFile.containsIniSetting` ("SeatDefaults", "autologin-user", u) `describe` "lightdm autologin"
src/Propellor/Property/List.hs view
@@ -1,86 +1,59 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ScopedTypeVariables #-} module Propellor.Property.List ( props,- PropertyList(..),- PropertyListType,- PropList(..),+ Props,+ toProps,+ propertyList,+ combineProperties, ) where import Propellor.Types-import Propellor.Engine+import Propellor.Types.Core+import Propellor.Types.MetaTypes import Propellor.PropAccum+import Propellor.Engine+import Propellor.Exception import Data.Monoid --- | Starts accumulating a list of properties.+toProps :: [Property (MetaTypes metatypes)] -> Props (MetaTypes metatypes)+toProps ps = Props (map toChildProperty ps)++-- | Combines a list of properties, resulting in a single property+-- that when run will run each property in the list in turn,+-- and print out the description of each as it's run. Does not stop+-- on failure; does propagate overall success/failure. --+-- For example:+-- -- > propertyList "foo" $ props--- > & someproperty--- > ! oldproperty--- > & otherproperty-props :: PropList-props = PropList []--data PropList = PropList [Property HasInfo]--instance PropAccum PropList where- PropList l `addProp` p = PropList (toProp p : l)- PropList l `addPropFront` p = PropList (l ++ [toProp p])- getProperties (PropList l) = reverse l--class PropertyList l where- -- | Combines a list of properties, resulting in a single property- -- that when run will run each property in the list in turn,- -- and print out the description of each as it's run. Does not stop- -- on failure; does propagate overall success/failure.- --- -- Note that Property HasInfo and Property NoInfo are not the same- -- type, and so cannot be mixed in a list. To make a list of- -- mixed types, which can also include RevertableProperty,- -- use `props`- propertyList :: Desc -> l -> Property (PropertyListType l)-- -- | Combines a list of properties, resulting in one property that- -- ensures each in turn. Stops if a property fails.- combineProperties :: Desc -> l -> Property (PropertyListType l)---- | Type level function to calculate whether a PropertyList has Info.-type family PropertyListType t-type instance PropertyListType [Property HasInfo] = HasInfo-type instance PropertyListType [Property NoInfo] = NoInfo-type instance PropertyListType [RevertableProperty NoInfo] = NoInfo-type instance PropertyListType [RevertableProperty HasInfo] = HasInfo-type instance PropertyListType PropList = HasInfo--instance PropertyList [Property NoInfo] where- propertyList desc ps = simpleProperty desc (ensureProperties ps) ps- combineProperties desc ps = simpleProperty desc (combineSatisfy ps NoChange) ps--instance PropertyList [Property HasInfo] where- -- It's ok to use ignoreInfo here, because the ps are made the- -- child properties of the property, and so their info is visible- -- that way.- propertyList desc ps = infoProperty desc (ensureProperties $ map ignoreInfo ps) mempty ps- combineProperties desc ps = infoProperty desc (combineSatisfy ps NoChange) mempty ps--instance PropertyList [RevertableProperty HasInfo] where- propertyList desc ps = propertyList desc (map setupRevertableProperty ps)- combineProperties desc ps = combineProperties desc (map setupRevertableProperty ps)--instance PropertyList [RevertableProperty NoInfo] where- propertyList desc ps = propertyList desc (map setupRevertableProperty ps)- combineProperties desc ps = combineProperties desc (map setupRevertableProperty ps)+-- > & bar+-- > & baz+propertyList :: SingI metatypes => Desc -> Props (MetaTypes metatypes) -> Property (MetaTypes metatypes)+propertyList desc (Props ps) = + property desc (ensureChildProperties cs)+ `addChildren` cs+ where+ cs = map toChildProperty ps -instance PropertyList PropList where- propertyList desc = propertyList desc . getProperties- combineProperties desc = combineProperties desc . getProperties+-- | Combines a list of properties, resulting in one property that+-- ensures each in turn. Stops if a property fails.+combineProperties :: SingI metatypes => Desc -> Props (MetaTypes metatypes) -> Property (MetaTypes metatypes)+combineProperties desc (Props ps) = + property desc (combineSatisfy cs NoChange)+ `addChildren` cs+ where+ cs = map toChildProperty ps -combineSatisfy :: [Property i] -> Result -> Propellor Result+combineSatisfy :: [ChildProperty] -> Result -> Propellor Result combineSatisfy [] rs = return rs-combineSatisfy (l:ls) rs = do- r <- ensureProperty $ ignoreInfo l+combineSatisfy (p:ps) rs = do+ r <- catchPropellor $ getSatisfy p case r of FailedChange -> return FailedChange- _ -> combineSatisfy ls (r <> rs)+ _ -> combineSatisfy ps (r <> rs)
src/Propellor/Property/Locale.hs view
@@ -21,14 +21,17 @@ -- -- Note that reverting this property does not make a locale unavailable. That's -- because it might be required for other Locale.selectedFor statements.-selectedFor :: Locale -> [LocaleVariable] -> RevertableProperty NoInfo+selectedFor :: Locale -> [LocaleVariable] -> RevertableProperty DebianLike DebianLike locale `selectedFor` vars = select <!> deselect where- select = check (not <$> isselected) (cmdProperty "update-locale" selectArgs)- `requires` available locale- `describe` (locale ++ " locale selected")- deselect = check isselected (cmdProperty "update-locale" vars)- `describe` (locale ++ " locale deselected")+ select = tightenTargets $ + check (not <$> isselected) + (cmdProperty "update-locale" selectArgs)+ `requires` available locale+ `describe` (locale ++ " locale selected")+ deselect = tightenTargets $+ check isselected (cmdProperty "update-locale" vars)+ `describe` (locale ++ " locale deselected") selectArgs = zipWith (++) vars (repeat ('=':locale)) isselected = locale `isSelectedFor` vars @@ -46,20 +49,21 @@ -- -- Per Debian bug #684134 we cannot ensure a locale is generated by means of -- Apt.reConfigure. So localeAvailable edits /etc/locale.gen manually.-available :: Locale -> RevertableProperty NoInfo-available locale = (ensureAvailable <!> ensureUnavailable)+available :: Locale -> RevertableProperty DebianLike DebianLike+available locale = ensureAvailable <!> ensureUnavailable where f = "/etc/locale.gen" desc = (locale ++ " locale generated")- ensureAvailable =- property desc $ (lines <$> (liftIO $ readFile f))- >>= \locales ->- if locale `presentIn` locales- then ensureProperty $- fileProperty desc (foldr uncomment []) f- `onChange` regenerate- else return FailedChange -- locale unavailable for generation- ensureUnavailable =+ ensureAvailable :: Property DebianLike+ ensureAvailable = property' desc $ \w -> do+ locales <- lines <$> (liftIO $ readFile f)+ if locale `presentIn` locales+ then ensureProperty w $+ fileProperty desc (foldr uncomment []) f+ `onChange` regenerate+ else return FailedChange -- locale unavailable for generation+ ensureUnavailable :: Property DebianLike+ ensureUnavailable = tightenTargets $ fileProperty (locale ++ " locale not generated") (foldr comment []) f `onChange` regenerate
src/Propellor/Property/Logcheck.hs view
@@ -28,9 +28,9 @@ ignoreFilePath :: ReportLevel -> Service -> FilePath ignoreFilePath t n = "/etc/logcheck/ignore.d." ++ (show t) </> n -ignoreLines :: ReportLevel -> Service -> [String] -> Property NoInfo+ignoreLines :: ReportLevel -> Service -> [String] -> Property UnixLike ignoreLines t n ls = (ignoreFilePath t n) `File.containsLines` ls `describe` ("logcheck ignore lines for " ++ n ++ "(" ++ (show t) ++ ")") -installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.installed ["logcheck"]
src/Propellor/Property/Mount.hs view
@@ -37,16 +37,17 @@ formatMountOpts (MountOpts l) = intercalate "," l -- | Mounts a device.-mounted :: FsType -> Source -> MountPoint -> MountOpts -> Property NoInfo+mounted :: FsType -> Source -> MountPoint -> MountOpts -> Property UnixLike 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]- `assume` MadeChange- `describe` ("bind mounted " ++ src ++ " to " ++ dest)+bindMount :: FilePath -> FilePath -> Property Linux+bindMount src dest = tightenTargets $+ cmdProperty "mount" ["--bind", src, dest]+ `assume` MadeChange+ `describe` ("bind mounted " ++ src ++ " to " ++ dest) mount :: FsType -> Source -> MountPoint -> MountOpts -> IO Bool mount fs src mnt opts = boolSystem "mount" $@@ -66,10 +67,10 @@ -- 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+fstabbed :: [MountPoint] -> [SwapPartition] -> Property Linux+fstabbed mnts swaps = property' "fstabbed" $ \o -> do fstab <- liftIO $ genFstab mnts swaps id- ensureProperty $ + ensureProperty o $ "/etc/fstab" `File.hasContent` fstab genFstab :: [MountPoint] -> [SwapPartition] -> (MountPoint -> MountPoint) -> IO [String]
src/Propellor/Property/Network.hs view
@@ -7,8 +7,8 @@ type Interface = String -ifUp :: Interface -> Property NoInfo-ifUp iface = cmdProperty "ifup" [iface]+ifUp :: Interface -> Property DebianLike+ifUp iface = tightenTargets $ cmdProperty "ifup" [iface] `assume` MadeChange -- | Resets /etc/network/interfaces to a clean and empty state,@@ -18,8 +18,8 @@ -- This can be used as a starting point to defining other interfaces. -- -- No interfaces are brought up or down by this property.-cleanInterfacesFile :: Property NoInfo-cleanInterfacesFile = hasContent interfacesFile+cleanInterfacesFile :: Property DebianLike+cleanInterfacesFile = tightenTargets $ hasContent interfacesFile [ "# Deployed by propellor, do not edit." , "" , "source-directory interfaces.d"@@ -31,8 +31,8 @@ `describe` ("clean " ++ interfacesFile) -- | Configures an interface to get its address via dhcp.-dhcp :: Interface -> Property NoInfo-dhcp iface = hasContent (interfaceDFile iface)+dhcp :: Interface -> Property DebianLike+dhcp iface = tightenTargets $ hasContent (interfaceDFile iface) [ "auto " ++ iface , "iface " ++ iface ++ " inet dhcp" ]@@ -50,18 +50,20 @@ -- -- (ipv6 addresses are not included because it's assumed they come up -- automatically in most situations.)-static :: Interface -> Property NoInfo-static iface = check (not <$> doesFileExist f) setup- `describe` desc- `requires` interfacesDEnabled+static :: Interface -> Property DebianLike+static iface = tightenTargets $ + check (not <$> doesFileExist f) setup+ `describe` desc+ `requires` interfacesDEnabled where f = interfaceDFile iface desc = "static " ++ iface- setup = property desc $ do+ setup :: Property DebianLike+ setup = property' desc $ \o -> do ls <- liftIO $ lines <$> readProcess "ip" ["-o", "addr", "show", iface, "scope", "global"] stanzas <- liftIO $ concat <$> mapM mkstanza ls- ensureProperty $ hasContent f $ ("auto " ++ iface) : stanzas+ ensureProperty o $ hasContent f $ ("auto " ++ iface) : stanzas mkstanza ipline = case words ipline of -- Note that the IP address is written CIDR style, so -- the netmask does not need to be specified separately.@@ -81,8 +83,8 @@ _ -> Nothing -- | 6to4 ipv6 connection, should work anywhere-ipv6to4 :: Property NoInfo-ipv6to4 = hasContent (interfaceDFile "sit0")+ipv6to4 :: Property DebianLike+ipv6to4 = tightenTargets $ hasContent (interfaceDFile "sit0") [ "# Deployed by propellor, do not edit." , "iface sit0 inet6 static" , "\taddress 2002:5044:5531::1"@@ -107,6 +109,8 @@ escapeInterfaceDName = filter (\c -> isAscii c && (isAlphaNum c || c `elem` "_-")) -- | Ensures that files in the the interfaces.d directory are used.-interfacesDEnabled :: Property NoInfo-interfacesDEnabled = containsLine interfacesFile "source-directory interfaces.d"- `describe` "interfaces.d directory enabled"+-- interfacesDEnabled :: Property DebianLike+interfacesDEnabled :: Property DebianLike+interfacesDEnabled = tightenTargets $+ containsLine interfacesFile "source-directory interfaces.d"+ `describe` "interfaces.d directory enabled"
src/Propellor/Property/Nginx.hs view
@@ -9,7 +9,7 @@ type ConfigFile = [String] -siteEnabled :: HostName -> ConfigFile -> RevertableProperty NoInfo+siteEnabled :: HostName -> ConfigFile -> RevertableProperty DebianLike DebianLike siteEnabled hn cf = enable <!> disable where enable = siteVal hn `File.isSymlinkedTo` siteValRelativeCfg hn@@ -22,11 +22,11 @@ `requires` installed `onChange` reloaded -siteAvailable :: HostName -> ConfigFile -> Property NoInfo-siteAvailable hn cf = ("nginx site available " ++ hn) ==>- siteCfg hn `File.hasContent` (comment : cf)+siteAvailable :: HostName -> ConfigFile -> Property DebianLike+siteAvailable hn cf = "nginx site available " ++ hn ==> tightenTargets go where comment = "# deployed with propellor, do not modify"+ go = siteCfg hn `File.hasContent` (comment : cf) siteCfg :: HostName -> FilePath siteCfg hn = "/etc/nginx/sites-available/" ++ hn@@ -37,11 +37,11 @@ siteValRelativeCfg :: HostName -> File.LinkTarget siteValRelativeCfg hn = File.LinkTarget ("../sites-available/" ++ hn) -installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.installed ["nginx"] -restarted :: Property NoInfo+restarted :: Property DebianLike restarted = Service.restarted "nginx" -reloaded :: Property NoInfo+reloaded :: Property DebianLike reloaded = Service.reloaded "nginx"
src/Propellor/Property/OS.hs view
@@ -46,7 +46,7 @@ -- install succeeds, to bootstrap from the cleanly installed system to -- a fully working system. For example: ----- > & os (System (Debian Unstable) "amd64")+-- > & osDebian Unstable "amd64" -- > & cleanInstallOnce (Confirmed "foo.example.com") -- > `onChange` propertyList "fixing up after clean install" -- > [ preserveNetwork@@ -64,7 +64,7 @@ -- > & User.accountFor "joey" -- > & User.hasSomePassword "joey" -- > -- rest of system properties here-cleanInstallOnce :: Confirmation -> Property NoInfo+cleanInstallOnce :: Confirmation -> Property Linux cleanInstallOnce confirmation = check (not <$> doesFileExist flagfile) $ go `requires` confirmed "clean install confirmed" confirmation where@@ -83,14 +83,18 @@ `requires` osbootstrapped - osbootstrapped = withOS (newOSDir ++ " bootstrapped") $ \o -> case o of- (Just d@(System (Debian _) _)) -> debootstrap d- (Just u@(System (Buntish _) _)) -> debootstrap u- _ -> unsupportedOS+ osbootstrapped :: Property Linux+ osbootstrapped = withOS (newOSDir ++ " bootstrapped") $ \w o -> case o of+ (Just d@(System (Debian _) _)) -> ensureProperty w $+ debootstrap d+ (Just u@(System (Buntish _) _)) -> ensureProperty w $+ debootstrap u+ _ -> unsupportedOS' - debootstrap targetos = ensureProperty $- -- Ignore the os setting, and install debootstrap from- -- source, since we don't know what OS we're running in yet.+ debootstrap :: System -> Property Linux+ debootstrap targetos =+ -- Install debootstrap from source, since we don't know+ -- what OS we're currently running in. Debootstrap.built' Debootstrap.sourceInstall newOSDir targetos Debootstrap.DefaultConfig -- debootstrap, I wish it was faster.. @@ -100,6 +104,7 @@ -- sync instead? -- This is the fun bit.+ flipped :: Property Linux flipped = property (newOSDir ++ " moved into place") $ liftIO $ do -- First, unmount most mount points, lazily, so -- they don't interfere with moving things around.@@ -137,6 +142,7 @@ return MadeChange + propellorbootstrapped :: Property UnixLike propellorbootstrapped = property "propellor re-debootstrapped in new os" $ return NoChange -- re-bootstrap propellor in /usr/local/propellor,@@ -145,6 +151,7 @@ -- be present in /old-os's /usr/local/propellor) -- TODO + finalized :: Property UnixLike finalized = property "clean OS installed" $ do liftIO $ writeFile flagfile "" return MadeChange@@ -179,7 +186,7 @@ data Confirmation = Confirmed HostName -confirmed :: Desc -> Confirmation -> Property NoInfo+confirmed :: Desc -> Confirmation -> Property UnixLike confirmed desc (Confirmed c) = property desc $ do hostname <- asks hostName if hostname /= c@@ -191,25 +198,26 @@ -- | </etc/network/interfaces> is configured to bring up the network -- interface that currently has a default route configured, using -- the same (static) IP address.-preserveNetwork :: Property NoInfo+preserveNetwork :: Property DebianLike preserveNetwork = go `requires` Network.cleanInterfacesFile where- go = property "preserve network configuration" $ do+ go :: Property DebianLike+ go = property' "preserve network configuration" $ \w -> do ls <- liftIO $ lines <$> readProcess "ip" ["route", "list", "scope", "global"] case words <$> headMaybe ls of Just ("default":"via":_:"dev":iface:_) ->- ensureProperty $ Network.static iface+ ensureProperty w $ Network.static iface _ -> do warningMessage "did not find any default ipv4 route" return FailedChange -- | </etc/resolv.conf> is copied from the old OS-preserveResolvConf :: Property NoInfo+preserveResolvConf :: Property Linux preserveResolvConf = check (fileExist oldloc) $- property (newloc ++ " copied from old OS") $ do+ property' (newloc ++ " copied from old OS") $ \w -> do ls <- liftIO $ lines <$> readFile oldloc- ensureProperty $ newloc `File.hasContent` ls+ ensureProperty w $ newloc `File.hasContent` ls where newloc = "/etc/resolv.conf" oldloc = oldOSDir ++ newloc@@ -217,20 +225,23 @@ -- | </root/.ssh/authorized_keys> has added to it any ssh keys that -- were authorized in the old OS. Any other contents of the file are -- retained.-preserveRootSshAuthorized :: Property NoInfo+preserveRootSshAuthorized :: Property UnixLike preserveRootSshAuthorized = check (fileExist oldloc) $- property (newloc ++ " copied from old OS") $ do+ property' desc $ \w -> do ks <- liftIO $ lines <$> readFile oldloc- ensureProperties (map (setupRevertableProperty . Ssh.authorizedKey (User "root")) ks)+ ensureProperty w $ combineProperties desc $+ toProps $ map (setupRevertableProperty . Ssh.authorizedKey (User "root")) ks where+ desc = newloc ++ " copied from old OS" newloc = "/root/.ssh/authorized_keys" oldloc = oldOSDir ++ newloc -- Removes the old OS's backup from </old-os>-oldOSRemoved :: Confirmation -> Property NoInfo+oldOSRemoved :: Confirmation -> Property UnixLike oldOSRemoved confirmation = check (doesDirectoryExist oldOSDir) $ go `requires` confirmed "old OS backup removal confirmed" confirmation where+ go :: Property UnixLike go = property "old OS backup removed" $ do liftIO $ removeDirectoryRecursive oldOSDir return MadeChange
src/Propellor/Property/Obnam.hs view
@@ -40,7 +40,7 @@ -- Since obnam uses a fair amount of system resources, only one obnam -- backup job will be run at a time. Other jobs will wait their turns to -- run.-backup :: FilePath -> Cron.Times -> [ObnamParam] -> NumClients -> Property NoInfo+backup :: FilePath -> Cron.Times -> [ObnamParam] -> NumClients -> Property DebianLike backup dir crontimes params numclients = backup' dir crontimes params numclients `requires` restored dir params@@ -50,7 +50,7 @@ -- -- The gpg secret key will be automatically imported -- into root's keyring using Propellor.Property.Gpg.keyImported-backupEncrypted :: FilePath -> Cron.Times -> [ObnamParam] -> NumClients -> Gpg.GpgKeyId -> Property HasInfo+backupEncrypted :: FilePath -> Cron.Times -> [ObnamParam] -> NumClients -> Gpg.GpgKeyId -> Property (HasInfo + DebianLike) backupEncrypted dir crontimes params numclients keyid = backup dir crontimes params' numclients `requires` Gpg.keyImported keyid (User "root")@@ -58,7 +58,7 @@ params' = ("--encrypt-with=" ++ Gpg.getGpgKeyId keyid) : params -- | Does a backup, but does not automatically restore.-backup' :: FilePath -> Cron.Times -> [ObnamParam] -> NumClients -> Property NoInfo+backup' :: FilePath -> Cron.Times -> [ObnamParam] -> NumClients -> Property DebianLike backup' dir crontimes params numclients = cronjob `describe` desc where desc = dir ++ " backed up by obnam"@@ -96,11 +96,12 @@ -- -- The restore is performed atomically; restoring to a temp directory -- and then moving it to the directory.-restored :: FilePath -> [ObnamParam] -> Property NoInfo-restored dir params = property (dir ++ " restored by obnam") go- `requires` installed+restored :: FilePath -> [ObnamParam] -> Property DebianLike+restored dir params = go `requires` installed where- go = ifM (liftIO needsRestore)+ desc = dir ++ " restored by obnam"+ go :: Property DebianLike+ go = property desc $ ifM (liftIO needsRestore) ( do warningMessage $ dir ++ " is empty/missing; restoring from backup ..." liftIO restore@@ -152,5 +153,5 @@ isKeepParam :: ObnamParam -> Bool isKeepParam p = "--keep=" `isPrefixOf` p -installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.installed ["obnam"]
src/Propellor/Property/OpenId.hs view
@@ -16,7 +16,7 @@ -- -- It's probably a good idea to put this property inside a docker or -- systemd-nspawn container.-providerFor :: [User] -> HostName -> Maybe Port -> Property HasInfo+providerFor :: [User] -> HostName -> Maybe Port -> Property (HasInfo + DebianLike) providerFor users hn mp = propertyList desc $ props & Apt.serviceInstalledRunning "apache2" & apacheconfigured@@ -24,7 +24,7 @@ `onChange` Apache.restarted & File.fileProperty (desc ++ " configured") (map setbaseurl) "/etc/simpleid/config.inc"- & propertyList desc (map identfile users)+ & propertyList desc (toProps $ map identfile users) where baseurl = hn ++ case mp of Nothing -> ""@@ -37,7 +37,7 @@ | otherwise = l apacheconfigured = case mp of- Nothing -> toProp $+ Nothing -> setupRevertableProperty $ Apache.virtualHost hn (Port 80) "/var/www/html" Just p -> propertyList desc $ props & Apache.listenPorts [p]
src/Propellor/Property/Parted.hs view
@@ -153,18 +153,17 @@ -- The FilePath can be a block device (eg, \/dev\/sda), or a disk image file. -- -- This deletes any existing partitions in the disk! Use with EXTREME caution!-partitioned :: Eep -> FilePath -> PartTable -> Property NoInfo-partitioned eep disk (PartTable tabletype parts) = property desc $ do+partitioned :: Eep -> FilePath -> PartTable -> Property DebianLike+partitioned eep disk (PartTable tabletype parts) = property' desc $ \w -> do isdev <- liftIO $ isBlockDevice <$> getFileStatus disk- ensureProperty $ combineProperties desc- [ parted eep disk partedparams- , if isdev+ ensureProperty w $ combineProperties desc $ props+ & parted eep disk partedparams+ & if isdev then formatl (map (\n -> disk ++ show n) [1 :: Int ..]) else Partition.kpartx disk (formatl . map Partition.partitionLoopDev)- ] where desc = disk ++ " partitioned"- formatl devs = combineProperties desc (map format (zip parts devs))+ formatl devs = combineProperties desc (toProps $ map format (zip parts devs)) partedparams = concat $ mklabel : mkparts (1 :: Integer) mempty parts [] format (p, dev) = Partition.formatted' (partMkFsOpts p) Partition.YesReallyFormatPartition (partFs p) dev@@ -193,12 +192,12 @@ -- -- Parted is run in script mode, so it will never prompt for input. -- It is asked to use cylinder alignment for the disk.-parted :: Eep -> FilePath -> [String] -> Property NoInfo+parted :: Eep -> FilePath -> [String] -> Property DebianLike parted YesReallyDeleteDiskContents disk ps = p `requires` installed where p = cmdProperty "parted" ("--script":"--align":"cylinder":disk:ps) `assume` MadeChange -- | Gets parted installed.-installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.installed ["parted"]
src/Propellor/Property/Partition.hs view
@@ -3,6 +3,7 @@ module Propellor.Property.Partition where import Propellor.Base+import Propellor.Types.Core import qualified Propellor.Property.Apt as Apt import Utility.Applicative @@ -16,7 +17,7 @@ data Eep = YesReallyFormatPartition -- | Formats a partition.-formatted :: Eep -> Fs -> FilePath -> Property NoInfo+formatted :: Eep -> Fs -> FilePath -> Property DebianLike formatted = formatted' [] -- | Options passed to a mkfs.* command when making a filesystem.@@ -24,7 +25,7 @@ -- Eg, ["-m0"] type MkfsOpts = [String] -formatted' :: MkfsOpts -> Eep -> Fs -> FilePath -> Property NoInfo+formatted' :: MkfsOpts -> Eep -> Fs -> FilePath -> Property DebianLike formatted' opts YesReallyFormatPartition fs dev = cmdProperty cmd opts' `assume` MadeChange `requires` Apt.installed [pkg]@@ -64,17 +65,18 @@ -- 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 -> ([LoopDev] -> Property NoInfo) -> Property NoInfo+kpartx :: FilePath -> ([LoopDev] -> Property DebianLike) -> Property DebianLike kpartx diskimage mkprop = go `requires` Apt.installed ["kpartx"] where- go = property (propertyDesc (mkprop [])) $ do+ go :: Property DebianLike+ go = property' (getDesc (mkprop [])) $ \w -> do cleanup -- idempotency 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)+ r <- ensureProperty w (mkprop loopdevs) cleanup return r cleanup = void $ liftIO $ boolSystem "kpartx" [Param "-d", File diskimage]
src/Propellor/Property/Postfix.hs view
@@ -12,13 +12,13 @@ import Data.List import Data.Char -installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.serviceInstalledRunning "postfix" -restarted :: Property NoInfo+restarted :: Property DebianLike restarted = Service.restarted "postfix" -reloaded :: Property NoInfo+reloaded :: Property DebianLike reloaded = Service.reloaded "postfix" -- | Configures postfix as a satellite system, which @@ -28,38 +28,39 @@ -- The smarthost may refuse to relay mail on to other domains, without -- further configuration/keys. But this should be enough to get cron job -- mail flowing to a place where it will be seen.-satellite :: Property NoInfo+satellite :: Property DebianLike satellite = check (not <$> mainCfIsSet "relayhost") setup `requires` installed where- setup = property "postfix satellite system" $ do+ desc = "postfix satellite system"+ setup :: Property DebianLike+ setup = property' desc $ \w -> do hn <- asks hostName let (_, domain) = separate (== '.') hn- ensureProperties- [ Apt.reConfigure "postfix"+ ensureProperty w $ combineProperties desc $ props+ & Apt.reConfigure "postfix" [ ("postfix/main_mailer_type", "select", "Satellite system") , ("postfix/root_address", "string", "root") , ("postfix/destinations", "string", "localhost") , ("postfix/mailname", "string", hn) ]- , mainCf ("relayhost", "smtp." ++ domain)+ & mainCf ("relayhost", "smtp." ++ domain) `onChange` reloaded- ] -- | Sets up a file by running a property (which the filename is passed -- to). If the setup property makes a change, postmap will be run on the -- file, and postfix will be reloaded. mappedFile- :: Combines (Property x) (Property NoInfo)+ :: Combines (Property x) (Property UnixLike) => FilePath -> (FilePath -> Property x)- -> Property (CInfo x NoInfo)+ -> CombinedType (Property x) (Property UnixLike) mappedFile f setup = setup f `onChange` (cmdProperty "postmap" [f] `assume` MadeChange) -- | Run newaliases command, which should be done after changing -- @/etc/aliases@.-newaliases :: Property NoInfo+newaliases :: Property UnixLike newaliases = check ("/etc/aliases" `isNewerThan` "/etc/aliases.db") (cmdProperty "newaliases" []) @@ -68,9 +69,9 @@ mainCfFile = "/etc/postfix/main.cf" -- | Sets a main.cf @name=value@ pair. Does not reload postfix immediately.-mainCf :: (String, String) -> Property NoInfo+mainCf :: (String, String) -> Property UnixLike mainCf (name, value) = check notset set- `describe` ("postfix main.cf " ++ setting)+ `describe` ("postfix main.cf " ++ setting) where setting = name ++ "=" ++ value notset = (/= Just value) <$> getMainCf name@@ -105,7 +106,7 @@ -- -- Note that multiline configurations that continue onto the next line -- are not currently supported.-dedupMainCf :: Property NoInfo+dedupMainCf :: Property UnixLike dedupMainCf = File.fileProperty "postfix main.cf dedupped" dedupCf mainCfFile dedupCf :: [String] -> [String]@@ -252,7 +253,7 @@ nws = length ws -- | Enables a `Service` in postfix's `masterCfFile`.-service :: Service -> RevertableProperty NoInfo+service :: Service -> RevertableProperty DebianLike DebianLike service s = (enable <!> disable) `describe` desc where@@ -276,7 +277,7 @@ -- It would be wise to enable fail2ban, for example: -- -- > Fail2Ban.jailEnabled "postfix-sasl"-saslAuthdInstalled :: Property NoInfo+saslAuthdInstalled :: Property DebianLike saslAuthdInstalled = setupdaemon `requires` Service.running "saslauthd" `requires` postfixgroup@@ -303,7 +304,7 @@ -- | Uses `saslpasswd2` to set the password for a user in the sasldb2 file. -- -- The password is taken from the privdata.-saslPasswdSet :: Domain -> User -> Property HasInfo+saslPasswdSet :: Domain -> User -> Property (HasInfo + UnixLike) saslPasswdSet domain (User user) = go `changesFileContent` "/etc/sasldb2" where go = withPrivData src ctx $ \getpw ->
src/Propellor/Property/PropellorRepo.hs view
@@ -11,7 +11,7 @@ -- -- This property is useful when hosts are being updated without using -- --spin, eg when using the `Propellor.Property.Cron.runPropellor` cron job.-hasOriginUrl :: String -> Property NoInfo+hasOriginUrl :: String -> Property UnixLike hasOriginUrl u = property ("propellor repo url " ++ u) $ do curru <- liftIO getRepoUrl if curru == Just u
src/Propellor/Property/Prosody.hs view
@@ -11,7 +11,7 @@ type Conf = String -confEnabled :: Conf -> ConfigFile -> RevertableProperty NoInfo+confEnabled :: Conf -> ConfigFile -> RevertableProperty DebianLike DebianLike confEnabled conf cf = enable <!> disable where enable = dir `File.isSymlinkedTo` target@@ -29,9 +29,9 @@ `requires` installed `onChange` reloaded -confAvailable :: Conf -> ConfigFile -> Property NoInfo+confAvailable :: Conf -> ConfigFile -> Property DebianLike confAvailable conf cf = ("prosody conf available " ++ conf) ==>- confAvailPath conf `File.hasContent` (comment : cf)+ tightenTargets (confAvailPath conf `File.hasContent` (comment : cf)) where comment = "-- deployed with propellor, do not modify" @@ -41,11 +41,11 @@ confValPath :: Conf -> FilePath confValPath conf = "/etc/prosody/conf.d" </> conf <.> "cfg.lua" -installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.installed ["prosody"] -restarted :: Property NoInfo+restarted :: Property DebianLike restarted = Service.restarted "prosody" -reloaded :: Property NoInfo+reloaded :: Property DebianLike reloaded = Service.reloaded "prosody"
src/Propellor/Property/Reboot.hs view
@@ -2,8 +2,8 @@ import Propellor.Base -now :: Property NoInfo-now = cmdProperty "reboot" []+now :: Property Linux+now = tightenTargets $ cmdProperty "reboot" [] `assume` MadeChange `describe` "reboot now" @@ -14,7 +14,7 @@ -- -- The reboot can be forced to run, which bypasses the init system. Useful -- if the init system might not be running for some reason.-atEnd :: Bool -> (Result -> Bool) -> Property NoInfo+atEnd :: Bool -> (Result -> Bool) -> Property Linux atEnd force resultok = property "scheduled reboot at end of propellor run" $ do endAction "rebooting" atend return NoChange
src/Propellor/Property/Rsync.hs view
@@ -16,7 +16,7 @@ -- | Ensures that the Dest directory exists and has identical contents as -- the Src directory.-syncDir :: Src -> Dest -> Property NoInfo+syncDir :: Src -> Dest -> Property DebianLike syncDir = syncDirFiltered [] data Filter @@ -43,7 +43,7 @@ -- Rsync checks each name to be transferred against its list of Filter -- rules, and the first matching one is acted on. If no matching rule -- is found, the file is processed.-syncDirFiltered :: [Filter] -> Src -> Dest -> Property NoInfo+syncDirFiltered :: [Filter] -> Src -> Dest -> Property DebianLike syncDirFiltered filters src dest = rsync $ [ "-av" -- Add trailing '/' to get rsync to sync the Dest directory,@@ -56,7 +56,7 @@ , "--quiet" ] ++ map toRsync filters -rsync :: [String] -> Property NoInfo+rsync :: [String] -> Property DebianLike rsync ps = cmdProperty "rsync" ps `assume` MadeChange `requires` Apt.installed ["rsync"]
src/Propellor/Property/Scheduled.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleContexts, ScopedTypeVariables #-} module Propellor.Property.Scheduled ( period@@ -10,6 +10,7 @@ ) where import Propellor.Base+import Propellor.Types.Core import Utility.Scheduled import Data.Time.Clock@@ -22,24 +23,24 @@ -- last run. period :: (IsProp (Property i)) => Property i -> Recurrance -> Property i period prop recurrance = flip describe desc $ adjustPropertySatisfy prop $ \satisfy -> do- lasttime <- liftIO $ getLastChecked (propertyDesc prop)+ lasttime <- liftIO $ getLastChecked (getDesc prop) nexttime <- liftIO $ fmap startTime <$> nextTime schedule lasttime t <- liftIO localNow if Just t >= nexttime then do r <- satisfy- liftIO $ setLastChecked t (propertyDesc prop)+ liftIO $ setLastChecked t (getDesc prop) return r else noChange where schedule = Schedule recurrance AnyTime- desc = propertyDesc prop ++ " (period " ++ fromRecurrance recurrance ++ ")"+ desc = getDesc prop ++ " (period " ++ fromRecurrance recurrance ++ ")" -- | Like period, but parse a human-friendly string.-periodParse :: Property NoInfo -> String -> Property NoInfo+periodParse :: (IsProp (Property i)) => Property i -> String -> Property i periodParse prop s = case toRecurrance s of Just recurrance -> period prop recurrance- Nothing -> property "periodParse" $ do+ Nothing -> adjustPropertySatisfy prop $ \_ -> do liftIO $ warningMessage $ "failed periodParse: " ++ s noChange
src/Propellor/Property/Service.hs view
@@ -11,17 +11,17 @@ -- Note that due to the general poor state of init scripts, the best -- we can do is try to start the service, and if it fails, assume -- this means it's already running.-running :: ServiceName -> Property NoInfo+running :: ServiceName -> Property DebianLike running = signaled "start" "running" -restarted :: ServiceName -> Property NoInfo+restarted :: ServiceName -> Property DebianLike restarted = signaled "restart" "restarted" -reloaded :: ServiceName -> Property NoInfo+reloaded :: ServiceName -> Property DebianLike reloaded = signaled "reload" "reloaded" -signaled :: String -> Desc -> ServiceName -> Property NoInfo-signaled cmd desc svc = p `describe` (desc ++ " " ++ svc)+signaled :: String -> Desc -> ServiceName -> Property DebianLike+signaled cmd desc svc = tightenTargets $ p `describe` (desc ++ " " ++ svc) where p = scriptProperty ["service " ++ shellEscape svc ++ " " ++ cmd ++ " >/dev/null 2>&1 || true"] `assume` NoChange
src/Propellor/Property/SiteSpecific/Branchable.hs view
@@ -9,7 +9,7 @@ import qualified Propellor.Property.Gpg as Gpg import qualified Propellor.Property.Sudo as Sudo -server :: [Host] -> Property HasInfo+server :: [Host] -> Property (HasInfo + DebianLike) server hosts = propertyList "branchable server" $ props & "/etc/timezone" `File.hasContent` ["Etc/UTC"] & "/etc/locale.gen" `File.containsLines`
src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs view
@@ -25,7 +25,7 @@ type TimeOut = String -- eg, 5h -autobuilder :: Architecture -> Times -> TimeOut -> Property HasInfo+autobuilder :: Architecture -> Times -> TimeOut -> Property (HasInfo + DebianLike) autobuilder arch crontimes timeout = combineProperties "gitannexbuilder" $ props & Apt.serviceInstalledRunning "cron" & Cron.niceJob "gitannexbuilder" crontimes (User builduser) gitbuilderdir@@ -37,6 +37,7 @@ -- The builduser account does not have a password set, -- instead use the password privdata to hold the rsync server -- password used to upload the built image.+ rsyncpassword :: Property (HasInfo + DebianLike) rsyncpassword = withPrivData (Password builduser) context $ \getpw -> property "rsync password" $ getpw $ \pw -> do have <- liftIO $ catchDefaultIO "" $@@ -46,7 +47,7 @@ then makeChange $ writeFile pwfile want else noChange -tree :: Architecture -> Flavor -> Property HasInfo+tree :: Architecture -> Flavor -> Property DebianLike tree buildarch flavor = combineProperties "gitannexbuilder tree" $ props & Apt.installed ["git"] & File.dirExists gitbuilderdir@@ -66,14 +67,14 @@ [ "git clone git://git-annex.branchable.com/ " ++ builddir ] -buildDepsApt :: Property HasInfo+buildDepsApt :: Property DebianLike buildDepsApt = combineProperties "gitannexbuilder build deps" $ props & Apt.buildDep ["git-annex"] & buildDepsNoHaskellLibs & Apt.buildDepIn builddir `describe` "git-annex source build deps installed" -buildDepsNoHaskellLibs :: Property NoInfo+buildDepsNoHaskellLibs :: Property DebianLike buildDepsNoHaskellLibs = Apt.installed ["git", "rsync", "moreutils", "ca-certificates", "debhelper", "ghc", "curl", "openssh-client", "git-remote-gcrypt",@@ -83,8 +84,9 @@ "libmagic-dev", "alex", "happy", "c2hs" ] -haskellPkgsInstalled :: String -> Property NoInfo-haskellPkgsInstalled dir = flagFile go ("/haskellpkgsinstalled")+haskellPkgsInstalled :: String -> Property DebianLike+haskellPkgsInstalled dir = tightenTargets $ + flagFile go ("/haskellpkgsinstalled") where go = userScriptProperty (User builduser) [ "cd " ++ builddir ++ " && ./standalone/" ++ dir ++ "/install-haskell-packages"@@ -93,7 +95,7 @@ -- Installs current versions of git-annex's deps from cabal, but only -- does so once.-cabalDeps :: Property NoInfo+cabalDeps :: Property UnixLike cabalDeps = flagFile go cabalupdated where go = userScriptProperty (User builduser)@@ -101,20 +103,20 @@ `assume` MadeChange cabalupdated = homedir </> ".cabal" </> "packages" </> "hackage.haskell.org" </> "00-index.cache" -autoBuilderContainer :: (System -> Flavor -> Property HasInfo) -> System -> Flavor -> Times -> TimeOut -> Systemd.Container-autoBuilderContainer mkprop osver@(System _ arch) flavor crontime timeout =- Systemd.container name osver (Chroot.debootstrapped mempty)- & mkprop osver flavor+autoBuilderContainer :: (DebianSuite -> Architecture -> Flavor -> Property (HasInfo + Debian)) -> DebianSuite -> Architecture -> Flavor -> Times -> TimeOut -> Systemd.Container+autoBuilderContainer mkprop suite arch flavor crontime timeout =+ Systemd.container name $ \d -> Chroot.debootstrapped mempty d $ props+ & mkprop suite arch flavor & autobuilder arch crontime timeout where name = arch ++ fromMaybe "" flavor ++ "-git-annex-builder" type Flavor = Maybe String -standardAutoBuilder :: System -> Flavor -> Property HasInfo-standardAutoBuilder osver@(System _ arch) flavor =+standardAutoBuilder :: DebianSuite -> Architecture -> Flavor -> Property (HasInfo + Debian)+standardAutoBuilder suite arch flavor = propertyList "standard git-annex autobuilder" $ props- & os osver+ & osDebian suite arch & buildDepsApt & Apt.stdSourcesList & Apt.unattendedUpgrades@@ -122,10 +124,10 @@ & User.accountFor (User builduser) & tree arch flavor -stackAutoBuilder :: System -> Flavor -> Property HasInfo-stackAutoBuilder osver@(System _ arch) flavor =+stackAutoBuilder :: DebianSuite -> Architecture -> Flavor -> Property (HasInfo + Debian)+stackAutoBuilder suite arch flavor = propertyList "git-annex autobuilder using stack" $ props- & os osver+ & osDebian suite arch & buildDepsNoHaskellLibs & Apt.stdSourcesList & Apt.unattendedUpgrades@@ -134,34 +136,34 @@ & tree arch flavor & stackInstalled -stackInstalled :: Property NoInfo-stackInstalled = withOS "stack installed" $ \o ->+stackInstalled :: Property Linux+stackInstalled = withOS "stack installed" $ \w o -> case o of (Just (System (Debian (Stable "jessie")) "i386")) ->- ensureProperty $ manualinstall "i386"- _ -> ensureProperty $ Apt.installed ["haskell-stack"]+ ensureProperty w $ manualinstall "i386"+ _ -> ensureProperty w $ Apt.installed ["haskell-stack"] where -- Warning: Using a binary downloaded w/o validation.- manualinstall arch = check (not <$> doesFileExist binstack) $- propertyList "stack installed from upstream tarball"- [ cmdProperty "wget" ["https://www.stackage.org/stack/linux-" ++ arch, "-O", tmptar]+ manualinstall :: Architecture -> Property Linux+ manualinstall arch = tightenTargets $ check (not <$> doesFileExist binstack) $+ propertyList "stack installed from upstream tarball" $ props+ & cmdProperty "wget" ["https://www.stackage.org/stack/linux-" ++ arch, "-O", tmptar] `assume` MadeChange- , File.dirExists tmpdir- , cmdProperty "tar" ["xf", tmptar, "-C", tmpdir, "--strip-components=1"]+ & File.dirExists tmpdir+ & cmdProperty "tar" ["xf", tmptar, "-C", tmpdir, "--strip-components=1"] `assume` MadeChange- , cmdProperty "mv" [tmpdir </> "stack", binstack]+ & cmdProperty "mv" [tmpdir </> "stack", binstack] `assume` MadeChange- , cmdProperty "rm" ["-rf", tmpdir, tmptar]+ & cmdProperty "rm" ["-rf", tmpdir, tmptar] `assume` MadeChange- ] binstack = "/usr/bin/stack" tmptar = "/root/stack.tar.gz" tmpdir = "/root/stack" -armAutoBuilder :: System -> Flavor -> Property HasInfo-armAutoBuilder osver flavor = +armAutoBuilder :: DebianSuite -> Architecture -> Flavor -> Property (HasInfo + Debian)+armAutoBuilder suite arch flavor = propertyList "arm git-annex autobuilder" $ props- & standardAutoBuilder osver flavor+ & standardAutoBuilder suite arch flavor & buildDepsNoHaskellLibs -- Works around ghc crash with parallel builds on arm. & (homedir </> ".cabal" </> "config")@@ -172,26 +174,30 @@ androidAutoBuilderContainer :: Times -> TimeOut -> Systemd.Container androidAutoBuilderContainer crontimes timeout =- androidContainer "android-git-annex-builder" (tree "android" Nothing) builddir- & Apt.unattendedUpgrades- & buildDepsNoHaskellLibs- & autobuilder "android" crontimes timeout+ androidAutoBuilderContainer' "android-git-annex-builder"+ (tree "android" Nothing) builddir crontimes timeout -- Android is cross-built in a Debian i386 container, using the Android NDK.-androidContainer- :: (IsProp (Property (CInfo NoInfo i)), (Combines (Property NoInfo) (Property i)))- => Systemd.MachineName- -> Property i+androidAutoBuilderContainer'+ :: Systemd.MachineName+ -> Property DebianLike -> FilePath+ -> Times+ -> TimeOut -> Systemd.Container-androidContainer name setupgitannexdir gitannexdir = Systemd.container name osver bootstrap- & Apt.stdSourcesList- & User.accountFor (User builduser)- & File.dirExists gitbuilderdir- & File.ownerGroup homedir (User builduser) (Group builduser)- & flagFile chrootsetup ("/chrootsetup")- `requires` setupgitannexdir- & haskellPkgsInstalled "android"+androidAutoBuilderContainer' name setupgitannexdir gitannexdir crontimes timeout = + Systemd.container name $ \d -> bootstrap d $ props+ & osDebian (Stable "jessie") "i386"+ & Apt.stdSourcesList+ & User.accountFor (User builduser)+ & File.dirExists gitbuilderdir+ & File.ownerGroup homedir (User builduser) (Group builduser)+ & flagFile chrootsetup ("/chrootsetup")+ `requires` setupgitannexdir+ & haskellPkgsInstalled "android"+ & Apt.unattendedUpgrades+ & buildDepsNoHaskellLibs+ & autobuilder "android" crontimes timeout where -- Use git-annex's android chroot setup script, which will install -- ghc-android and the NDK, all build deps, etc, in the home@@ -200,5 +206,4 @@ [ "cd " ++ gitannexdir ++ " && ./standalone/android/buildchroot-inchroot" ] `assume` MadeChange- osver = System (Debian (Stable "jessie")) "i386" bootstrap = Chroot.debootstrapped mempty
src/Propellor/Property/SiteSpecific/GitHome.hs view
@@ -5,14 +5,15 @@ import Propellor.Property.User -- | Clones Joey Hess's git home directory, and runs its fixups script.-installedFor :: User -> Property NoInfo+installedFor :: User -> Property DebianLike installedFor user@(User u) = check (not <$> hasGitDir user) $ - property ("githome " ++ u) (go =<< liftIO (homedir user))- `requires` Apt.installed ["git"]+ go `requires` Apt.installed ["git"] where- go home = do+ go :: Property DebianLike+ go = property' ("githome " ++ u) $ \w -> do+ home <- liftIO (homedir user) let tmpdir = home </> "githome"- ensureProperty $ combineProperties "githome setup"+ ensureProperty w $ combineProperties "githome setup" $ toProps [ userScriptProperty user ["git clone " ++ url ++ " " ++ tmpdir] `assume` MadeChange , property "moveout" $ makeChange $ void $
src/Propellor/Property/SiteSpecific/IABak.hs view
@@ -15,14 +15,14 @@ userrepo :: String userrepo = "git@gitlab.com:archiveteam/IA.bak.users.git" -publicFace :: Property HasInfo+publicFace :: Property DebianLike publicFace = propertyList "iabak public face" $ props & Git.cloned (User "root") repo "/usr/local/IA.BAK" (Just "server") & Apt.serviceInstalledRunning "apache2" & Cron.niceJob "graph-gen" (Cron.Times "*/10 * * * *") (User "root") "/" "/usr/local/IA.BAK/web/graph-gen.sh" -gitServer :: [Host] -> Property HasInfo+gitServer :: [Host] -> Property (HasInfo + DebianLike) gitServer knownhosts = propertyList "iabak git server" $ props & Git.cloned (User "root") repo "/usr/local/IA.BAK" (Just "server") & Git.cloned (User "root") repo "/usr/local/IA.BAK/client" (Just "master")@@ -42,7 +42,7 @@ "/usr/local/IA.BAK" "./expireemailer" -registrationServer :: [Host] -> Property HasInfo+registrationServer :: [Host] -> Property (HasInfo + DebianLike) registrationServer knownhosts = propertyList "iabak registration server" $ props & User.accountFor (User "registrar") & Ssh.userKeys (User "registrar") (Context "IA.bak.users.git") sshKeys@@ -66,7 +66,7 @@ [ (SshRsa, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCoiE+CPiIQyfWnl/E9iKG3eo4QzlH30vi7xAgKolGaTu6qKy4XPtl+8MNm2Dqn9QEYRVyyOT/XH0yP5dRc6uyReT8dBy03MmLkVbj8Q+nKCz5YOMTxrY3sX6RRXU1zVGjeVd0DtC+rKRT7reoCxef42LAJTm8nCyZu/enAuso5qHqBbqulFz2YXEKfU1SEEXLawtvgGck1KmCyg+pqazeI1eHWXrojQf5isTBKfPQLWVppBkWAf5cA4wP5U1vN9dVirIdw66ds1M8vnGlkTBjxP/HLGBWGYhZHE7QXjXRsk2RIXlHN9q6GdNu8+F3HXS22mst47E4UAeRoiXSMMtF5") ] -graphiteServer :: Property HasInfo+graphiteServer :: Property (HasInfo + DebianLike) graphiteServer = propertyList "iabak graphite server" $ props & Apt.serviceInstalledRunning "apache2" & Apt.installed ["libapache2-mod-wsgi", "graphite-carbon", "graphite-web"]@@ -114,7 +114,8 @@ , "</VirtualHost>" ] where+ graphiteCSRF :: Property (HasInfo + DebianLike) graphiteCSRF = withPrivData (Password "csrf-token") (Context "iabak.archiveteam.org") $- \gettoken -> property "graphite-web CSRF token" $- gettoken $ \token -> ensureProperty $ File.containsLine+ \gettoken -> property' "graphite-web CSRF token" $ \w ->+ gettoken $ \token -> ensureProperty w $ File.containsLine "/etc/graphite/local_settings.py" ("SECRET_KEY = '"++ privDataVal token ++"'")
src/Propellor/Property/SiteSpecific/JoeySites.hs view
@@ -1,6 +1,8 @@ -- | Specific configuration for Joey Hess's sites. Probably not useful to -- others except as an example. +{-# LANGUAGE FlexibleContexts, TypeFamilies #-}+ module Propellor.Property.SiteSpecific.JoeySites where import Propellor.Base@@ -18,13 +20,14 @@ import qualified Propellor.Property.Postfix as Postfix import qualified Propellor.Property.Systemd as Systemd import qualified Propellor.Property.Fail2Ban as Fail2Ban+import qualified Propellor.Property.LetsEncrypt as LetsEncrypt import Utility.FileMode import Data.List import System.Posix.Files import Data.String.Utils -scrollBox :: Property HasInfo+scrollBox :: Property (HasInfo + DebianLike) scrollBox = propertyList "scroll server" $ props & User.accountFor (User "scroll") & Git.cloned (User "scroll") "git://git.kitenet.net/scroll" (d </> "scroll") Nothing@@ -94,16 +97,12 @@ s = d </> "login.sh" g = d </> "game.sh" -oldUseNetServer :: [Host] -> Property HasInfo+oldUseNetServer :: [Host] -> Property (HasInfo + DebianLike) oldUseNetServer hosts = propertyList "olduse.net server" $ props & Apt.installed ["leafnode"] & oldUseNetInstalled "oldusenet-server" & oldUseNetBackup- & check (not . isSymbolicLink <$> getSymbolicLinkStatus newsspool)- (property "olduse.net spool in place" $ makeChange $ do- removeDirectoryRecursive newsspool- createSymbolicLink (datadir </> "news") newsspool- )+ & spoolsymlink & "/etc/news/leafnode/config" `File.hasContent` [ "# olduse.net configuration (deployed by propellor)" , "expire = 1000000" -- no expiry via texpire@@ -127,7 +126,7 @@ , "find -type d -empty | xargs --no-run-if-empty rmdir" ] uucpcommand = "/usr/bin/uucp " ++ datadir- nntpcfg = apachecfg "nntp.olduse.net" False+ nntpcfg = apachecfg "nntp.olduse.net" [ " DocumentRoot " ++ datadir ++ "/" , " <Directory " ++ datadir ++ "/>" , " Options Indexes FollowSymlinks"@@ -135,7 +134,15 @@ , Apache.allowAll , " </Directory>" ]+ + spoolsymlink :: Property UnixLike+ spoolsymlink = check (not . isSymbolicLink <$> getSymbolicLinkStatus newsspool)+ (property "olduse.net spool in place" $ makeChange $ do+ removeDirectoryRecursive newsspool+ createSymbolicLink (datadir </> "news") newsspool+ ) + oldUseNetBackup :: Property (HasInfo + DebianLike) oldUseNetBackup = Obnam.backup datadir (Cron.Times "33 4 * * *") [ "--repository=sftp://2318@usw-s002.rsync.net/~/olduse.net" , "--client-name=spool"@@ -149,12 +156,12 @@ `requires` Ssh.knownHost hosts "usw-s002.rsync.net" (User "root") keyfile = "/root/.ssh/olduse.net.key" -oldUseNetShellBox :: Property HasInfo+oldUseNetShellBox :: Property DebianLike oldUseNetShellBox = propertyList "olduse.net shellbox" $ props & oldUseNetInstalled "oldusenet" & Service.running "shellinabox" -oldUseNetInstalled :: Apt.Package -> Property HasInfo+oldUseNetInstalled :: Apt.Package -> Property DebianLike oldUseNetInstalled pkg = check (not <$> Apt.isInstalled pkg) $ propertyList ("olduse.net " ++ pkg) $ props & Apt.installed (words "build-essential devscripts debhelper git libncursesw5-dev libpcre3-dev pkg-config bison libicu-dev libidn11-dev libcanlock2-dev libuu-dev ghc libghc-strptime-dev libghc-hamlet-dev libghc-ifelse-dev libghc-hxt-dev libghc-utf8-string-dev libghc-missingh-dev libghc-sha-dev")@@ -170,25 +177,25 @@ ] `assume` MadeChange `describe` "olduse.net built"--kgbServer :: Property HasInfo+ +kgbServer :: Property (HasInfo + Debian) kgbServer = propertyList desc $ props & installed & File.hasPrivContent "/etc/kgb-bot/kgb.conf" anyContext `onChange` Service.restarted "kgb-bot" where desc = "kgb.kitenet.net setup"- installed = withOS desc $ \o -> case o of+ installed :: Property Debian+ installed = withOS desc $ \w o -> case o of (Just (System (Debian Unstable) _)) ->- ensureProperty $ propertyList desc- [ Apt.serviceInstalledRunning "kgb-bot"- , "/etc/default/kgb-bot" `File.containsLine` "BOT_ENABLED=1"+ ensureProperty w $ propertyList desc $ props+ & Apt.serviceInstalledRunning "kgb-bot"+ & "/etc/default/kgb-bot" `File.containsLine` "BOT_ENABLED=1" `describe` "kgb bot enabled" `onChange` Service.running "kgb-bot"- ] _ -> error "kgb server needs Debian unstable (for kgb-bot 1.31+)" -mumbleServer :: [Host] -> Property HasInfo+mumbleServer :: [Host] -> Property (HasInfo + DebianLike) mumbleServer hosts = combineProperties hn $ props & Apt.serviceInstalledRunning "mumble-server" & Obnam.backup "/var/lib/mumble-server" (Cron.Times "55 5 * * *")@@ -209,7 +216,7 @@ sshkey = "/root/.ssh/mumble.debian.net.key" -- git.kitenet.net and git.joeyh.name-gitServer :: [Host] -> Property HasInfo+gitServer :: [Host] -> Property (HasInfo + DebianLike) gitServer hosts = propertyList "git.kitenet.net setup" $ props & Obnam.backupEncrypted "/srv/git" (Cron.Times "33 3 * * *") [ "--repository=sftp://2318@usw-s002.rsync.net/~/git.kitenet.net"@@ -246,14 +253,14 @@ & Apache.modEnabled "cgi" where sshkey = "/root/.ssh/git.kitenet.net.key"- website hn = apacheSite hn True- [ " DocumentRoot /srv/web/git.kitenet.net/"+ website hn = Apache.httpsVirtualHost' hn "/srv/web/git.kitenet.net/" letos+ [ Apache.iconDir , " <Directory /srv/web/git.kitenet.net/>" , " Options Indexes ExecCGI FollowSymlinks" , " AllowOverride None" , " AddHandler cgi-script .cgi" , " DirectoryIndex index.cgi"- , Apache.allowAll+ , Apache.allowAll , " </Directory>" , "" , " ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/"@@ -266,7 +273,7 @@ type AnnexUUID = String -- | A website, with files coming from a git-annex repository.-annexWebSite :: Git.RepoUrl -> HostName -> AnnexUUID -> [(String, Git.RepoUrl)] -> Property HasInfo+annexWebSite :: Git.RepoUrl -> HostName -> AnnexUUID -> [(String, Git.RepoUrl)] -> Property (HasInfo + DebianLike) annexWebSite origin hn uuid remotes = propertyList (hn ++" website using git-annex") $ props & Git.cloned (User "joey") origin dir Nothing `onChange` setup@@ -290,65 +297,44 @@ , "git update-server-info" ] addremote (name, url) = "git remote add " ++ shellEscape name ++ " " ++ shellEscape url- setupapache = apacheSite hn True+ setupapache = Apache.httpsVirtualHost' hn dir letos [ " ServerAlias www."++hn- , ""- , " DocumentRoot /srv/web/"++hn- , " <Directory /srv/web/"++hn++">"- , " Options FollowSymLinks"- , " AllowOverride None"- , Apache.allowAll- , " </Directory>"- , " <Directory /srv/web/"++hn++">"+ , Apache.iconDir+ , " <Directory "++dir++">" , " Options Indexes FollowSymLinks ExecCGI" , " AllowOverride None" , " AddHandler cgi-script .cgi" , " DirectoryIndex index.html index.cgi"- , Apache.allowAll+ , Apache.allowAll , " </Directory>" ] -apacheSite :: HostName -> Bool -> Apache.ConfigFile -> RevertableProperty NoInfo-apacheSite hn withssl middle = Apache.siteEnabled hn $ apachecfg hn withssl middle+letos :: LetsEncrypt.AgreeTOS+letos = LetsEncrypt.AgreeTOS (Just "id@joeyh.name") -apachecfg :: HostName -> Bool -> Apache.ConfigFile -> Apache.ConfigFile-apachecfg hn withssl middle- | withssl = vhost False ++ vhost True- | otherwise = vhost False- where- vhost ssl = - [ "<VirtualHost *:"++show port++">"- , " ServerAdmin grue@joeyh.name"- , " ServerName "++hn++":"++show port- ]- ++ mainhttpscert ssl- ++ middle ++- [ ""- , " ErrorLog /var/log/apache2/error.log"- , " LogLevel warn"- , " CustomLog /var/log/apache2/access.log combined"- , " ServerSignature On"- , " "- , " <Directory \"/usr/share/apache2/icons\">"- , " Options Indexes MultiViews"- , " AllowOverride None"- , Apache.allowAll- , " </Directory>"- , "</VirtualHost>"- ]- where- port = if ssl then 443 else 80 :: Int+apacheSite :: HostName -> Apache.ConfigFile -> RevertableProperty DebianLike DebianLike+apacheSite hn middle = Apache.siteEnabled hn $ apachecfg hn middle -mainhttpscert :: Bool -> Apache.ConfigFile-mainhttpscert False = []-mainhttpscert True = - [ " SSLEngine on"- , " SSLCertificateFile /etc/ssl/certs/web.pem"- , " SSLCertificateKeyFile /etc/ssl/private/web.pem"- , " SSLCertificateChainFile /etc/ssl/certs/startssl.pem"+apachecfg :: HostName -> Apache.ConfigFile -> Apache.ConfigFile+apachecfg hn middle = + [ "<VirtualHost *:"++show port++">"+ , " ServerAdmin grue@joeyh.name"+ , " ServerName "++hn++":"++show port ]+ ++ middle +++ [ ""+ , " ErrorLog /var/log/apache2/error.log"+ , " LogLevel warn"+ , " CustomLog /var/log/apache2/access.log combined"+ , " ServerSignature On"+ , " "+ , Apache.iconDir+ , "</VirtualHost>"+ ]+ where+ port = 80 :: Int -gitAnnexDistributor :: Property HasInfo+gitAnnexDistributor :: Property (HasInfo + DebianLike) gitAnnexDistributor = combineProperties "git-annex distributor, including rsync server and signer" $ props & Apt.installed ["rsync"] & File.hasPrivContent "/etc/rsyncd.conf" (Context "git-annex distributor")@@ -364,19 +350,18 @@ -- git-annex distribution signing key & Gpg.keyImported (Gpg.GpgKeyId "89C809CB") (User "joey") where- endpoint d = combineProperties ("endpoint " ++ d)- [ File.dirExists d- , File.ownerGroup d (User "joey") (Group "joey")- ]+ endpoint d = combineProperties ("endpoint " ++ d) $ props+ & File.dirExists d+ & File.ownerGroup d (User "joey") (Group "joey") -downloads :: [Host] -> Property HasInfo+downloads :: [Host] -> Property (HasInfo + DebianLike) downloads hosts = annexWebSite "/srv/git/downloads.git" "downloads.kitenet.net" "840760dc-08f0-11e2-8c61-576b7e66acfd" [("eubackup", "ssh://eubackup.kitenet.net/~/lib/downloads/")] `requires` Ssh.knownHost hosts "eubackup.kitenet.net" (User "joey") -tmp :: Property HasInfo+tmp :: Property (HasInfo + DebianLike) tmp = propertyList "tmp.kitenet.net" $ props & annexWebSite "/srv/git/joey/tmp.git" "tmp.kitenet.net"@@ -386,7 +371,7 @@ & pumpRss -- Twitter, you kill us.-twitRss :: Property HasInfo+twitRss :: Property DebianLike twitRss = combineProperties "twitter rss" $ props & Git.cloned (User "joey") "git://git.kitenet.net/twitrss.git" dir Nothing & check (not <$> doesFileExist (dir </> "twitRss")) compiled@@ -409,11 +394,11 @@ ] -- Work around for expired ssl cert.-pumpRss :: Property NoInfo+pumpRss :: Property DebianLike pumpRss = Cron.job "pump rss" (Cron.Times "15 * * * *") (User "joey") "/srv/web/tmp.kitenet.net/" "wget https://rss.io.jpope.org/feed/joeyh@identi.ca.atom -O pump.atom.new --no-check-certificate 2>/dev/null; sed 's/ & / /g' pump.atom.new > pump.atom" -ircBouncer :: Property HasInfo+ircBouncer :: Property (HasInfo + DebianLike) ircBouncer = propertyList "IRC bouncer" $ props & Apt.installed ["znc"] & User.accountFor (User "znc")@@ -428,20 +413,19 @@ where conf = "/home/znc/.znc/configs/znc.conf" -kiteShellBox :: Property NoInfo-kiteShellBox = propertyList "kitenet.net shellinabox"- [ Apt.installed ["openssl", "shellinabox", "openssh-client"]- , File.hasContent "/etc/default/shellinabox"+kiteShellBox :: Property DebianLike+kiteShellBox = propertyList "kitenet.net shellinabox" $ props+ & Apt.installed ["openssl", "shellinabox", "openssh-client"]+ & File.hasContent "/etc/default/shellinabox" [ "# Deployed by propellor" , "SHELLINABOX_DAEMON_START=1" , "SHELLINABOX_PORT=443" , "SHELLINABOX_ARGS=\"--no-beep --service=/:SSH:kitenet.net\"" ] `onChange` Service.restarted "shellinabox"- , Service.running "shellinabox"- ]+ & Service.running "shellinabox" -githubBackup :: Property HasInfo+githubBackup :: Property (HasInfo + DebianLike) githubBackup = propertyList "github-backup box" $ props & Apt.installed ["github-backup", "moreutils"] & githubKeys@@ -462,7 +446,7 @@ ] ++ map gitriddance githubMirrors gitriddance (r, msg) = "(cd " ++ r ++ " && gitriddance " ++ shellEscape msg ++ ")" -githubKeys :: Property HasInfo+githubKeys :: Property (HasInfo + UnixLike) githubKeys = let f = "/home/joey/.github-keys" in File.hasPrivContent f anyContext@@ -482,12 +466,12 @@ where plzuseurl u = "Please submit changes to " ++ u ++ " instead of using github pull requests, which are not part of my workflow. Just open a todo item there and link to a git repository containing your changes. Did you know, git is a distributed system? The git repository doesn't even need to be on github! Please send any complaints to Github; they don't allow turning off pull requests or redirecting them elsewhere. -- A robot acting on behalf of Joey Hess" -rsyncNetBackup :: [Host] -> Property NoInfo+rsyncNetBackup :: [Host] -> Property DebianLike rsyncNetBackup hosts = Cron.niceJob "rsync.net copied in daily" (Cron.Times "30 5 * * *") (User "joey") "/home/joey/lib/backup" "mkdir -p rsync.net && rsync --delete -az 2318@usw-s002.rsync.net: rsync.net" `requires` Ssh.knownHost hosts "usw-s002.rsync.net" (User "joey") -backupsBackedupFrom :: [Host] -> HostName -> FilePath -> Property NoInfo+backupsBackedupFrom :: [Host] -> HostName -> FilePath -> Property DebianLike backupsBackedupFrom hosts srchost destdir = Cron.niceJob desc (Cron.Times "@reboot") (User "joey") "/" cmd `requires` Ssh.knownHost hosts srchost (User "joey")@@ -495,9 +479,9 @@ desc = "backups copied from " ++ srchost ++ " on boot" cmd = "sleep 30m && rsync -az --bwlimit=300K --partial --delete " ++ srchost ++ ":lib/backup/ " ++ destdir </> srchost -obnamRepos :: [String] -> Property NoInfo-obnamRepos rs = propertyList ("obnam repos for " ++ unwords rs)- (mkbase : map mkrepo rs)+obnamRepos :: [String] -> Property UnixLike+obnamRepos rs = propertyList ("obnam repos for " ++ unwords rs) $+ toProps (mkbase : map mkrepo rs) where mkbase = mkdir "/home/joey/lib/backup" `requires` mkdir "/home/joey/lib"@@ -505,13 +489,13 @@ mkdir d = File.dirExists d `before` File.ownerGroup d (User "joey") (Group "joey") -podcatcher :: Property NoInfo+podcatcher :: Property DebianLike podcatcher = Cron.niceJob "podcatcher run hourly" (Cron.Times "55 * * * *") (User "joey") "/home/joey/lib/sound/podcasts" "xargs git-annex importfeed -c annex.genmetadata=true < feeds; mr --quiet update" `requires` Apt.installed ["git-annex", "myrepos"] -kiteMailServer :: Property HasInfo+kiteMailServer :: Property (HasInfo + DebianLike) kiteMailServer = propertyList "kitenet.net mail server" $ props & Postfix.installed & Apt.installed ["postfix-pcre"]@@ -710,7 +694,7 @@ -- Configures postfix to relay outgoing mail to kitenet.net, with -- verification via tls cert.-postfixClientRelay :: Context -> Property HasInfo+postfixClientRelay :: Context -> Property (HasInfo + DebianLike) postfixClientRelay ctx = Postfix.mainCfFile `File.containsLines` -- Using smtps not smtp because more networks firewall smtp [ "relayhost = kitenet.net:smtps"@@ -727,7 +711,7 @@ `requires` hasPostfixCert ctx -- Configures postfix to have the dkim milter, and no other milters.-dkimMilter :: Property HasInfo+dkimMilter :: Property (HasInfo + DebianLike) dkimMilter = Postfix.mainCfFile `File.containsLines` [ "smtpd_milters = inet:localhost:8891" , "non_smtpd_milters = inet:localhost:8891"@@ -740,7 +724,7 @@ -- This does not configure postfix to use the dkim milter, -- nor does it set up domainkey DNS.-dkimInstalled :: Property HasInfo+dkimInstalled :: Property (HasInfo + DebianLike) dkimInstalled = go `onChange` Service.restarted "opendkim" where go = propertyList "opendkim installed" $ props@@ -763,36 +747,25 @@ domainKey :: (BindDomain, Record) domainKey = (RelDomain "mail._domainkey", TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCc+/rfzNdt5DseBBmfB3C6sVM7FgVvf4h1FeCfyfwPpVcmPdW6M2I+NtJsbRkNbEICxiP6QY2UM0uoo9TmPqLgiCCG2vtuiG6XMsS0Y/gGwqKM7ntg/7vT1Go9vcquOFFuLa5PnzpVf8hB9+PMFdS4NPTvWL2c5xxshl/RJzICnQIDAQAB") -hasJoeyCAChain :: Property HasInfo+hasJoeyCAChain :: Property (HasInfo + UnixLike) hasJoeyCAChain = "/etc/ssl/certs/joeyca.pem" `File.hasPrivContentExposed` Context "joeyca.pem" -hasPostfixCert :: Context -> Property HasInfo-hasPostfixCert ctx = combineProperties "postfix tls cert installed"- [ "/etc/ssl/certs/postfix.pem" `File.hasPrivContentExposed` ctx- , "/etc/ssl/private/postfix.pem" `File.hasPrivContent` ctx- ]--kitenetHttps :: Property HasInfo-kitenetHttps = propertyList "kitenet.net https certs" $ props- & File.hasPrivContent "/etc/ssl/certs/web.pem" ctx- & File.hasPrivContent "/etc/ssl/private/web.pem" ctx- & File.hasPrivContent "/etc/ssl/certs/startssl.pem" ctx- & Apache.modEnabled "ssl"- where- ctx = Context "kitenet.net"+hasPostfixCert :: Context -> Property (HasInfo + UnixLike)+hasPostfixCert ctx = combineProperties "postfix tls cert installed" $ props+ & "/etc/ssl/certs/postfix.pem" `File.hasPrivContentExposed` ctx+ & "/etc/ssl/private/postfix.pem" `File.hasPrivContent` ctx -- Legacy static web sites and redirections from kitenet.net to newer -- sites.-legacyWebSites :: Property HasInfo+legacyWebSites :: Property (HasInfo + DebianLike) legacyWebSites = propertyList "legacy web sites" $ props & Apt.serviceInstalledRunning "apache2" & Apache.modEnabled "rewrite" & Apache.modEnabled "cgi" & Apache.modEnabled "speling" & userDirHtml- & kitenetHttps- & apacheSite "kitenet.net" True+ & Apache.httpsVirtualHost' "kitenet.net" "/var/www" letos -- /var/www is empty [ "DocumentRoot /var/www" , "<Directory /var/www>"@@ -880,7 +853,7 @@ , "rewriterule /~kyle/family/wiki(.*) http://macleawiki.branchable.com$1 [L]" ] & alias "anna.kitenet.net"- & apacheSite "anna.kitenet.net" False+ & apacheSite "anna.kitenet.net" [ "DocumentRoot /home/anna/html" , "<Directory /home/anna/html/>" , " Options Indexes ExecCGI"@@ -890,7 +863,7 @@ ] & alias "sows-ear.kitenet.net" & alias "www.sows-ear.kitenet.net"- & apacheSite "sows-ear.kitenet.net" False+ & apacheSite "sows-ear.kitenet.net" [ "ServerAlias www.sows-ear.kitenet.net" , "DocumentRoot /srv/web/sows-ear.kitenet.net" , "<Directory /srv/web/sows-ear.kitenet.net>"@@ -903,7 +876,7 @@ ] & alias "wortroot.kitenet.net" & alias "www.wortroot.kitenet.net"- & apacheSite "wortroot.kitenet.net" False+ & apacheSite "wortroot.kitenet.net" [ "ServerAlias www.wortroot.kitenet.net" , "DocumentRoot /srv/web/wortroot.kitenet.net" , "<Directory /srv/web/wortroot.kitenet.net>"@@ -913,7 +886,7 @@ , "</Directory>" ] & alias "creeksidepress.com"- & apacheSite "creeksidepress.com" False+ & apacheSite "creeksidepress.com" [ "ServerAlias www.creeksidepress.com" , "DocumentRoot /srv/web/www.creeksidepress.com" , "<Directory /srv/web/www.creeksidepress.com>"@@ -923,7 +896,7 @@ , "</Directory>" ] & alias "joey.kitenet.net"- & apacheSite "joey.kitenet.net" False+ & apacheSite "joey.kitenet.net" [ "DocumentRoot /var/www" , "<Directory /var/www/>" , " Options Indexes ExecCGI"@@ -944,7 +917,7 @@ , "rewriterule (.*) http://joeyh.name$1 [r]" ] -userDirHtml :: Property NoInfo+userDirHtml :: Property DebianLike userDirHtml = File.fileProperty "apache userdir is html" (map munge) conf `onChange` Apache.reloaded `requires` Apache.modEnabled "userdir"@@ -956,10 +929,9 @@ -- <http://joeyh.name/blog/entry/a_programmable_alarm_clock_using_systemd/> -- -- oncalendar example value: "*-*-* 7:30"-alarmClock :: String -> User -> String -> Property NoInfo-alarmClock oncalendar (User user) command = combineProperties- "goodmorning timer installed"- [ "/etc/systemd/system/goodmorning.timer" `File.hasContent`+alarmClock :: String -> User -> String -> Property DebianLike+alarmClock oncalendar (User user) command = combineProperties "goodmorning timer installed" $ props+ & "/etc/systemd/system/goodmorning.timer" `File.hasContent` [ "[Unit]" , "Description=good morning" , ""@@ -974,7 +946,7 @@ ] `onChange` (Systemd.daemonReloaded `before` Systemd.restarted "goodmorning.timer")- , "/etc/systemd/system/goodmorning.service" `File.hasContent`+ & "/etc/systemd/system/goodmorning.service" `File.hasContent` [ "[Unit]" , "Description=good morning" , "RefuseManualStart=true"@@ -987,8 +959,7 @@ , "ExecStart=/bin/systemd-inhibit --what=handle-lid-switch --why=goodmorning /bin/su " ++ user ++ " -c \"" ++ command ++ "\"" ] `onChange` Systemd.daemonReloaded- , Systemd.enabled "goodmorning.timer"- , Systemd.started "goodmorning.timer"- , "/etc/systemd/logind.conf" `ConfFile.containsIniSetting`+ & Systemd.enabled "goodmorning.timer"+ & Systemd.started "goodmorning.timer"+ & "/etc/systemd/logind.conf" `ConfFile.containsIniSetting` ("Login", "LidSwitchIgnoreInhibited", "no")- ]
src/Propellor/Property/Ssh.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveDataTypeable, TypeFamilies #-} module Propellor.Property.Ssh ( installed,@@ -47,10 +47,13 @@ import qualified Data.Set as S import Data.List -installed :: Property NoInfo-installed = Apt.installed ["ssh"]+installed :: Property UnixLike+installed = "ssh installed" ==> (aptinstall `pickOS` unsupportedOS)+ where+ aptinstall :: Property DebianLike+ aptinstall = Apt.installed ["ssh"] -restarted :: Property NoInfo+restarted :: Property DebianLike restarted = Service.restarted "ssh" sshBool :: Bool -> String@@ -62,10 +65,10 @@ type ConfigKeyword = String -setSshdConfigBool :: ConfigKeyword -> Bool -> Property NoInfo+setSshdConfigBool :: ConfigKeyword -> Bool -> Property DebianLike setSshdConfigBool setting allowed = setSshdConfig setting (sshBool allowed) -setSshdConfig :: ConfigKeyword -> String -> Property NoInfo+setSshdConfig :: ConfigKeyword -> String -> Property DebianLike setSshdConfig setting val = File.fileProperty desc f sshdConfig `onChange` restarted where@@ -84,19 +87,19 @@ | WithoutPassword -- ^ disable password authentication for root, while allowing other authentication methods | ForcedCommandsOnly -- ^ allow root login with public-key authentication, but only if a forced command has been specified for the public key -permitRootLogin :: RootLogin -> Property NoInfo+permitRootLogin :: RootLogin -> Property DebianLike permitRootLogin (RootLogin b) = setSshdConfigBool "PermitRootLogin" b permitRootLogin WithoutPassword = setSshdConfig "PermitRootLogin" "without-password" permitRootLogin ForcedCommandsOnly = setSshdConfig "PermitRootLogin" "forced-commands-only" -passwordAuthentication :: Bool -> Property NoInfo+passwordAuthentication :: Bool -> Property DebianLike passwordAuthentication = setSshdConfigBool "PasswordAuthentication" -- | Configure ssh to not allow password logins. -- -- To prevent lock-out, this is done only once root's -- authorized_keys is in place.-noPasswords :: Property NoInfo+noPasswords :: Property DebianLike noPasswords = check (hasAuthorizedKeys (User "root")) $ passwordAuthentication False @@ -114,7 +117,7 @@ -- ports it is configured to listen on. -- -- Revert to prevent it listening on a particular port.-listenPort :: Port -> RevertableProperty NoInfo+listenPort :: Port -> RevertableProperty DebianLike DebianLike listenPort port = enable <!> disable where portline = "Port " ++ fromPort port@@ -133,16 +136,17 @@ -- | Blows away existing host keys and make new ones. -- Useful for systems installed from an image that might reuse host keys. -- A flag file is used to only ever do this once.-randomHostKeys :: Property NoInfo+randomHostKeys :: Property DebianLike randomHostKeys = flagFile prop "/etc/ssh/.unique_host_keys" `onChange` restarted where- prop = property "ssh random host keys" $ do+ prop :: Property UnixLike+ prop = property' "ssh random host keys" $ \w -> do void $ liftIO $ boolSystem "sh" [ Param "-c" , Param "rm -f /etc/ssh/ssh_host_*" ]- ensureProperty $ scriptProperty [ "DPKG_MAINTSCRIPT_NAME=postinst DPKG_MAINTSCRIPT_PACKAGE=openssh-server /var/lib/dpkg/info/openssh-server.postinst configure" ]+ ensureProperty w $ scriptProperty [ "DPKG_MAINTSCRIPT_NAME=postinst DPKG_MAINTSCRIPT_PACKAGE=openssh-server /var/lib/dpkg/info/openssh-server.postinst configure" ] `assume` MadeChange -- | The text of a ssh public key, for example, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB3BJ2GqZiTR2LEoDXyYFgh/BduWefjdKXAsAtzS9zeI"@@ -153,43 +157,51 @@ -- The corresponding private keys come from the privdata. -- -- Any host keys that are not in the list are removed from the host.-hostKeys :: IsContext c => c -> [(SshKeyType, PubKeyText)] -> Property HasInfo-hostKeys ctx l = propertyList desc $ catMaybes $- map (\(t, pub) -> Just $ hostKey ctx t pub) l ++ [cleanup]+hostKeys :: IsContext c => c -> [(SshKeyType, PubKeyText)] -> Property (HasInfo + DebianLike)+hostKeys ctx l = go `before` cleanup where desc = "ssh host keys configured " ++ typelist (map fst l)+ go :: Property (HasInfo + DebianLike)+ go = propertyList desc $ toProps $ catMaybes $+ map (\(t, pub) -> Just $ hostKey ctx t pub) l typelist tl = "(" ++ unwords (map fromKeyType tl) ++ ")" alltypes = [minBound..maxBound] staletypes = let have = map fst l in filter (`notElem` have) alltypes- removestale b = map (File.notPresent . flip keyFile b) staletypes+ removestale :: Bool -> [Property DebianLike]+ removestale b = map (tightenTargets . File.notPresent . flip keyFile b) staletypes+ cleanup :: Property DebianLike cleanup- | null staletypes || null l = Nothing- | otherwise = Just $ toProp $- property ("any other ssh host keys removed " ++ typelist staletypes) $- ensureProperty $- combineProperties desc (removestale True ++ removestale False)- `onChange` restarted+ | null staletypes || null l = doNothing+ | otherwise =+ combineProperties ("any other ssh host keys removed " ++ typelist staletypes)+ (toProps $ removestale True ++ removestale False)+ `onChange` restarted -- | Installs a single ssh host key of a particular type. -- -- The public key is provided to this function; -- the private key comes from the privdata;-hostKey :: IsContext c => c -> SshKeyType -> PubKeyText -> Property HasInfo-hostKey context keytype pub = combineProperties desc- [ hostPubKey keytype pub- , toProp $ property desc $ install File.hasContent True (lines pub)- , withPrivData (keysrc "" (SshPrivKey keytype "")) context $ \getkey ->- property desc $ getkey $- install File.hasContentProtected False . privDataLines- ]- `onChange` restarted+hostKey :: IsContext c => c -> SshKeyType -> PubKeyText -> Property (HasInfo + DebianLike)+hostKey context keytype pub = go `onChange` restarted where+ go = combineProperties desc $ props+ & hostPubKey keytype pub+ & installpub+ & installpriv desc = "ssh host key configured (" ++ fromKeyType keytype ++ ")"- install writer ispub keylines = do- let f = keyFile keytype ispub- ensureProperty $ writer f (keyFileContent keylines) keysrc ext field = PrivDataSourceFileFromCommand field ("sshkey"++ext) ("ssh-keygen -t " ++ sshKeyTypeParam keytype ++ " -f sshkey")+ installpub :: Property UnixLike+ installpub = keywriter File.hasContent True (lines pub)+ installpriv :: Property (HasInfo + UnixLike)+ installpriv = withPrivData (keysrc "" (SshPrivKey keytype "")) context $ \getkey ->+ property' desc $ \w -> getkey $+ ensureProperty w+ . keywriter File.hasContentProtected False+ . privDataLines+ keywriter p ispub keylines = do+ let f = keyFile keytype ispub+ p f (keyFileContent keylines) -- Make sure that there is a newline at the end; -- ssh requires this for some types of private keys.@@ -204,7 +216,7 @@ -- | Indicates the host key that is used by a Host, but does not actually -- configure the host to use it. Normally this does not need to be used; -- use 'hostKey' instead.-hostPubKey :: SshKeyType -> PubKeyText -> Property HasInfo+hostPubKey :: SshKeyType -> PubKeyText -> Property (HasInfo + UnixLike) hostPubKey t = pureInfoProperty "ssh pubkey known" . HostKeyInfo . M.singleton t getHostPubKey :: Propellor (M.Map SshKeyType PubKeyText)@@ -224,7 +236,7 @@ -- parameter when there is a duplicate key HostKeyInfo (new `M.union` old) -userPubKeys :: User -> [(SshKeyType, PubKeyText)] -> Property HasInfo+userPubKeys :: User -> [(SshKeyType, PubKeyText)] -> Property (HasInfo + UnixLike) userPubKeys u@(User n) l = pureInfoProperty ("ssh pubkey for " ++ n) $ UserKeyInfo (M.singleton u (S.fromList l)) @@ -248,8 +260,8 @@ -- -- The public keys are added to the Info, so other properties like -- `authorizedKeysFrom` can use them.-userKeys :: IsContext c => User -> c -> [(SshKeyType, PubKeyText)] -> Property HasInfo-userKeys user@(User name) context ks = combineProperties desc $+userKeys :: IsContext c => User -> c -> [(SshKeyType, PubKeyText)] -> Property (HasInfo + UnixLike)+userKeys user@(User name) context ks = combineProperties desc $ toProps $ userPubKeys user ks : map (userKeyAt Nothing user context) ks where desc = unwords@@ -264,7 +276,7 @@ -- A file can be specified to write the key to somewhere other than -- the default locations. Allows a user to have multiple keys for -- different roles.-userKeyAt :: IsContext c => Maybe FilePath -> User -> c -> (SshKeyType, PubKeyText) -> Property HasInfo+userKeyAt :: IsContext c => Maybe FilePath -> User -> c -> (SshKeyType, PubKeyText) -> Property (HasInfo + UnixLike) userKeyAt dest user@(User u) context (keytype, pubkeytext) = combineProperties desc $ props & pubkey@@ -276,17 +288,21 @@ , dest , Just $ "(" ++ fromKeyType keytype ++ ")" ]- pubkey = property desc $ install File.hasContent ".pub" [pubkeytext]- privkey = withPrivData (SshPrivKey keytype u) context $ \getkey ->- property desc $ getkey $- install File.hasContentProtected "" . privDataLines- install writer ext key = do+ pubkey :: Property UnixLike+ pubkey = property' desc $ \w -> + ensureProperty w =<< installprop File.hasContent ".pub" [pubkeytext]+ privkey :: Property (HasInfo + UnixLike)+ privkey = withPrivData (SshPrivKey keytype u) context privkey'+ privkey' :: ((PrivData -> Propellor Result) -> Propellor Result) -> Property (HasInfo + UnixLike)+ privkey' getkey = property' desc $ \w -> getkey $ \k ->+ ensureProperty w+ =<< installprop File.hasContentProtected "" (privDataLines k)+ installprop writer ext key = do f <- liftIO $ keyfile ext- ensureProperty $ combineProperties desc- [ writer f (keyFileContent key)- , File.ownerGroup f user (userGroup user)- , File.ownerGroup (takeDirectory f) user (userGroup user)- ]+ return $ combineProperties desc $ props+ & writer f (keyFileContent key)+ & File.ownerGroup f user (userGroup user)+ & File.ownerGroup (takeDirectory f) user (userGroup user) keyfile ext = case dest of Nothing -> do home <- homeDirectory <$> getUserEntryForName u@@ -301,33 +317,34 @@ -- | Puts some host's ssh public key(s), as set using `hostPubKey` -- or `hostKey` into the known_hosts file for a user.-knownHost :: [Host] -> HostName -> User -> Property NoInfo-knownHost hosts hn user@(User u) = property desc $- go =<< knownHostLines hosts hn+knownHost :: [Host] -> HostName -> User -> Property UnixLike+knownHost hosts hn user@(User u) = property' desc $ \w ->+ go w =<< knownHostLines hosts hn where desc = u ++ " knows ssh key for " ++ hn - go [] = do+ go _ [] = do warningMessage $ "no configured ssh host keys for " ++ hn return FailedChange- go ls = do+ go w ls = do f <- liftIO $ dotFile "known_hosts" user- modKnownHost user f $+ ensureProperty w $ modKnownHost user f $ f `File.containsLines` ls `requires` File.dirExists (takeDirectory f) -- | Reverts `knownHost`-unknownHost :: [Host] -> HostName -> User -> Property NoInfo-unknownHost hosts hn user@(User u) = property desc $- go =<< knownHostLines hosts hn+unknownHost :: [Host] -> HostName -> User -> Property UnixLike+unknownHost hosts hn user@(User u) = property' desc $ \w ->+ go w =<< knownHostLines hosts hn where desc = u ++ " does not know ssh key for " ++ hn - go [] = return NoChange- go ls = do+ go _ [] = return NoChange+ go w ls = do f <- liftIO $ dotFile "known_hosts" user ifM (liftIO $ doesFileExist f)- ( modKnownHost user f $ f `File.lacksLines` ls+ ( ensureProperty w $ modKnownHost user f $+ f `File.lacksLines` ls , return NoChange ) @@ -337,8 +354,8 @@ keylines (Just m) = map (\k -> hn ++ " " ++ k) (M.elems m) keylines Nothing = [] -modKnownHost :: User -> FilePath -> Property NoInfo -> Propellor Result-modKnownHost user f p = ensureProperty $ p+modKnownHost :: User -> FilePath -> Property UnixLike -> Property UnixLike+modKnownHost user f p = p `requires` File.ownerGroup f user (userGroup user) `requires` File.ownerGroup (takeDirectory f) user (userGroup user) @@ -348,30 +365,30 @@ -- The ssh keys of the remote user can be set using `keysImported` -- -- Any other lines in the authorized_keys file are preserved as-is.-authorizedKeysFrom :: User -> (User, Host) -> Property NoInfo+authorizedKeysFrom :: User -> (User, Host) -> Property UnixLike localuser@(User ln) `authorizedKeysFrom` (remoteuser@(User rn), remotehost) =- property desc (go =<< authorizedKeyLines remoteuser remotehost)+ property' desc (\w -> go w =<< authorizedKeyLines remoteuser remotehost) where remote = rn ++ "@" ++ hostName remotehost desc = ln ++ " authorized_keys from " ++ remote - go [] = do+ go _ [] = do warningMessage $ "no configured ssh user keys for " ++ remote return FailedChange- go ls = ensureProperty $ combineProperties desc $- map (authorizedKey localuser) ls+ go w ls = ensureProperty w $ combineProperties desc $ toProps $+ map (setupRevertableProperty . authorizedKey localuser) ls -- | Reverts `authorizedKeysFrom`-unauthorizedKeysFrom :: User -> (User, Host) -> Property NoInfo+unauthorizedKeysFrom :: User -> (User, Host) -> Property UnixLike localuser@(User ln) `unauthorizedKeysFrom` (remoteuser@(User rn), remotehost) =- property desc (go =<< authorizedKeyLines remoteuser remotehost)+ property' desc (\w -> go w =<< authorizedKeyLines remoteuser remotehost) where remote = rn ++ "@" ++ hostName remotehost desc = ln ++ " unauthorized_keys from " ++ remote - go [] = return NoChange- go ls = ensureProperty $ combineProperties desc $- map (revert . authorizedKey localuser) ls+ go _ [] = return NoChange+ go w ls = ensureProperty w $ combineProperties desc $ toProps $+ map (undoRevertableProperty . authorizedKey localuser) ls authorizedKeyLines :: User -> Host -> Propellor [File.Line] authorizedKeyLines remoteuser remotehost =@@ -380,37 +397,37 @@ -- | Makes a user have authorized_keys from the PrivData -- -- This removes any other lines from the file.-authorizedKeys :: IsContext c => User -> c -> Property HasInfo+authorizedKeys :: IsContext c => User -> c -> Property (HasInfo + UnixLike) authorizedKeys user@(User u) context = withPrivData (SshAuthorizedKeys u) context $ \get ->- property desc $ get $ \v -> do+ property' desc $ \w -> get $ \v -> do f <- liftIO $ dotFile "authorized_keys" user- ensureProperty $ combineProperties desc- [ File.hasContentProtected f (keyFileContent (privDataLines v))- , File.ownerGroup f user (userGroup user)- , File.ownerGroup (takeDirectory f) user (userGroup user)- ]+ ensureProperty w $ combineProperties desc $ props+ & File.hasContentProtected f (keyFileContent (privDataLines v))+ & File.ownerGroup f user (userGroup user)+ & File.ownerGroup (takeDirectory f) user (userGroup user) where desc = u ++ " has authorized_keys" -- | Ensures that a user's authorized_keys contains a line. -- Any other lines in the file are preserved as-is.-authorizedKey :: User -> String -> RevertableProperty NoInfo+authorizedKey :: User -> String -> RevertableProperty UnixLike UnixLike authorizedKey user@(User u) l = add <!> remove where- add = property (u ++ " has authorized_keys") $ do+ add = property' (u ++ " has authorized_keys") $ \w -> do f <- liftIO $ dotFile "authorized_keys" user- modAuthorizedKey f user $+ ensureProperty w $ modAuthorizedKey f user $ f `File.containsLine` l `requires` File.dirExists (takeDirectory f)- remove = property (u ++ " lacks authorized_keys") $ do+ remove = property' (u ++ " lacks authorized_keys") $ \w -> do f <- liftIO $ dotFile "authorized_keys" user ifM (liftIO $ doesFileExist f)- ( modAuthorizedKey f user $ f `File.lacksLine` l+ ( ensureProperty w $ modAuthorizedKey f user $+ f `File.lacksLine` l , return NoChange ) -modAuthorizedKey :: FilePath -> User -> Property NoInfo -> Propellor Result-modAuthorizedKey f user p = ensureProperty $ p+modAuthorizedKey :: FilePath -> User -> Property UnixLike -> Property UnixLike+modAuthorizedKey f user p = p `before` File.mode f (combineModes [ownerWriteMode, ownerReadMode]) `before` File.ownerGroup f user (userGroup user) `before` File.ownerGroup (takeDirectory f) user (userGroup user)
src/Propellor/Property/Sudo.hs view
@@ -9,12 +9,13 @@ -- | Allows a user to sudo. If the user has a password, sudo is configured -- to require it. If not, NOPASSWORD is enabled for the user.-enabledFor :: User -> Property NoInfo-enabledFor user@(User u) = property desc go `requires` Apt.installed ["sudo"]+enabledFor :: User -> Property DebianLike+enabledFor user@(User u) = go `requires` Apt.installed ["sudo"] where- go = do+ go :: Property UnixLike+ go = property' desc $ \w -> do locked <- liftIO $ isLockedPassword user- ensureProperty $+ ensureProperty w $ fileProperty desc (modify locked . filter (wanted locked)) "/etc/sudoers"
src/Propellor/Property/Systemd.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleInstances, TypeFamilies #-} module Propellor.Property.Systemd ( -- * Services@@ -25,6 +25,7 @@ MachineName, Container, container,+ debContainer, nspawned, -- * Container configuration containerCfg,@@ -43,6 +44,7 @@ import Propellor.Base import Propellor.Types.Chroot import Propellor.Types.Container+import Propellor.Container import Propellor.Types.Info import qualified Propellor.Property.Chroot as Chroot import qualified Propellor.Property.Apt as Apt@@ -61,23 +63,23 @@ data Container = Container MachineName Chroot.Chroot Host deriving (Show) -instance PropAccum Container where- (Container n c h) `addProp` p = Container n c (h `addProp` p)- (Container n c h) `addPropFront` p = Container n c (h `addPropFront` p)- getProperties (Container _ _ h) = hostProperties h+instance IsContainer Container where+ containerProperties (Container _ _ h) = containerProperties h+ containerInfo (Container _ _ h) = containerInfo h+ setContainerProperties (Container n c h) ps = Container n c (setContainerProperties h ps) -- | Starts a systemd service. -- -- Note that this does not configure systemd to start the service on boot, -- it only ensures that the service is currently running.-started :: ServiceName -> Property NoInfo-started n = cmdProperty "systemctl" ["start", n]+started :: ServiceName -> Property Linux+started n = tightenTargets $ cmdProperty "systemctl" ["start", n] `assume` NoChange `describe` ("service " ++ n ++ " started") -- | Stops a systemd service.-stopped :: ServiceName -> Property NoInfo-stopped n = cmdProperty "systemctl" ["stop", n]+stopped :: ServiceName -> Property Linux+stopped n = tightenTargets $ cmdProperty "systemctl" ["stop", n] `assume` NoChange `describe` ("service " ++ n ++ " stopped") @@ -85,35 +87,35 @@ -- -- This does not ensure the service is started, it only configures systemd -- to start it on boot.-enabled :: ServiceName -> Property NoInfo-enabled n = cmdProperty "systemctl" ["enable", n]+enabled :: ServiceName -> Property Linux+enabled n = tightenTargets $ cmdProperty "systemctl" ["enable", n] `assume` NoChange `describe` ("service " ++ n ++ " enabled") -- | Disables a systemd service.-disabled :: ServiceName -> Property NoInfo-disabled n = cmdProperty "systemctl" ["disable", n]+disabled :: ServiceName -> Property Linux+disabled n = tightenTargets $ cmdProperty "systemctl" ["disable", n] `assume` NoChange `describe` ("service " ++ n ++ " disabled") -- | Masks a systemd service.-masked :: ServiceName -> RevertableProperty NoInfo+masked :: ServiceName -> RevertableProperty Linux Linux masked n = systemdMask <!> systemdUnmask where- systemdMask = cmdProperty "systemctl" ["mask", n]+ systemdMask = tightenTargets $ cmdProperty "systemctl" ["mask", n] `assume` NoChange `describe` ("service " ++ n ++ " masked")- systemdUnmask = cmdProperty "systemctl" ["unmask", n]+ systemdUnmask = tightenTargets $ cmdProperty "systemctl" ["unmask", n] `assume` NoChange `describe` ("service " ++ n ++ " unmasked") -- | Ensures that a service is both enabled and started-running :: ServiceName -> Property NoInfo+running :: ServiceName -> Property Linux running n = started n `requires` enabled n -- | Restarts a systemd service.-restarted :: ServiceName -> Property NoInfo-restarted n = cmdProperty "systemctl" ["restart", n]+restarted :: ServiceName -> Property Linux+restarted n = tightenTargets $ cmdProperty "systemctl" ["restart", n] `assume` NoChange `describe` ("service " ++ n ++ " restarted") @@ -126,16 +128,15 @@ journald = "systemd-journald" -- | Enables persistent storage of the journal.-persistentJournal :: Property NoInfo+persistentJournal :: Property DebianLike persistentJournal = check (not <$> doesDirectoryExist dir) $- combineProperties "persistent systemd journal"- [ cmdProperty "install" ["-d", "-g", "systemd-journal", dir]+ combineProperties "persistent systemd journal" $ props+ & cmdProperty "install" ["-d", "-g", "systemd-journal", dir] `assume` MadeChange- , cmdProperty "setfacl" ["-R", "-nm", "g:adm:rx,d:g:adm:rx", dir]+ & Apt.installed ["acl"]+ & cmdProperty "setfacl" ["-R", "-nm", "g:adm:rx,d:g:adm:rx", dir] `assume` MadeChange- , started "systemd-journal-flush"- ]- `requires` Apt.installed ["acl"]+ & started "systemd-journal-flush" where dir = "/var/log/journal" @@ -148,11 +149,10 @@ -- currently the case for files like journald.conf and system.conf. -- And it assumes the file already exists with -- the right [Header], so new lines can just be appended to the end.-configured :: FilePath -> Option -> String -> Property NoInfo-configured cfgfile option value = combineProperties desc- [ File.fileProperty desc (mapMaybe removeother) cfgfile- , File.containsLine cfgfile line- ]+configured :: FilePath -> Option -> String -> Property Linux+configured cfgfile option value = tightenTargets $ combineProperties desc $ props+ & File.fileProperty desc (mapMaybe removeother) cfgfile+ & File.containsLine cfgfile line where setting = option ++ "=" line = setting ++ value@@ -162,44 +162,61 @@ | otherwise = Just l -- | Causes systemd to reload its configuration files.-daemonReloaded :: Property NoInfo-daemonReloaded = cmdProperty "systemctl" ["daemon-reload"]+daemonReloaded :: Property Linux+daemonReloaded = tightenTargets $ cmdProperty "systemctl" ["daemon-reload"] `assume` NoChange -- | Configures journald, restarting it so the changes take effect.-journaldConfigured :: Option -> String -> Property NoInfo+journaldConfigured :: Option -> String -> Property Linux journaldConfigured option value = configured "/etc/systemd/journald.conf" option value `onChange` restarted journald -- | Ensures machined and machinectl are installed-machined :: Property NoInfo-machined = withOS "machined installed" $ \o ->+machined :: Property Linux+machined = withOS "machined installed" $ \w o -> case o of -- Split into separate debian package since systemd 225. (Just (System (Debian suite) _))- | not (isStable suite) -> ensureProperty $+ | not (isStable suite) -> ensureProperty w $ Apt.installed ["systemd-container"] _ -> noChange --- | Defines a container with a given machine name, and operating system,+-- | Defines a container with a given machine name, -- and how to create its chroot if not already present. ----- Properties can be added to configure the Container.+-- Properties can be added to configure the Container. At a minimum,+-- add a property such as `osDebian` to specify the operating system+-- to bootstrap. ----- > container "webserver" (System (Debian Unstable) "amd64") (Chroot.debootstrapped mempty)+-- > container "webserver" $ \d -> Chroot.debootstrapped mempty d $ props+-- > & osDebian Unstable "amd64" -- > & Apt.installedRunning "apache2" -- > & ...-container :: MachineName -> System -> (FilePath -> Chroot.Chroot) -> Container-container name system mkchroot = Container name c h- & os system- & resolvConfed- & linkJournal+container :: MachineName -> (FilePath -> Chroot.Chroot) -> Container+container name mkchroot = + let c = Container name chroot h+ in setContainerProps c $ containerProps c+ &^ resolvConfed+ &^ linkJournal where- c = mkchroot (containerDir name)- & os system- h = Host name [] mempty+ chroot = mkchroot (containerDir name)+ h = Host name (containerProperties chroot) (containerInfo chroot) +-- | Defines a container with a given machine name, with the chroot+-- created using debootstrap.+--+-- Properties can be added to configure the Container. At a minimum,+-- add a property such as `osDebian` to specify the operating system+-- to bootstrap.+--+-- > debContainer "webserver" $ props+-- > & osDebian Unstable "amd64"+-- > & Apt.installedRunning "apache2"+-- > & ...+debContainer :: MachineName -> Props metatypes -> Container+debContainer name ps = container name $ \d -> Chroot.debootstrapped mempty d ps+ -- | Runs a container using systemd-nspawn. -- -- A systemd unit is set up for the container, so it will automatically@@ -214,13 +231,14 @@ -- -- Reverting this property stops the container, removes the systemd unit, -- and deletes the chroot and all its contents.-nspawned :: Container -> RevertableProperty HasInfo+nspawned :: Container -> RevertableProperty (HasInfo + Linux) Linux nspawned c@(Container name (Chroot.Chroot loc builder _) h) = p `describe` ("nspawned " ++ name) where+ p :: RevertableProperty (HasInfo + Linux) Linux p = enterScript c `before` chrootprovisioned- `before` nspawnService c (_chrootCfg $ getInfo $ hostInfo h)+ `before` nspawnService c (_chrootCfg $ fromInfo $ hostInfo h) `before` containerprovisioned -- Chroot provisioning is run in systemd-only mode,@@ -230,8 +248,9 @@ -- Use nsenter to enter container and and run propellor to -- finish provisioning.+ containerprovisioned :: RevertableProperty Linux Linux containerprovisioned =- Chroot.propellChroot chroot (enterContainerProcess c) False+ tightenTargets (Chroot.propellChroot chroot (enterContainerProcess c) False) <!> doNothing @@ -239,7 +258,7 @@ -- | Sets up the service file for the container, and then starts -- it running.-nspawnService :: Container -> ChrootCfg -> RevertableProperty NoInfo+nspawnService :: Container -> ChrootCfg -> RevertableProperty Linux Linux nspawnService (Container name _ _) cfg = setup <!> teardown where service = nspawnServiceName name@@ -264,10 +283,12 @@ <$> servicefilecontent <*> catchDefaultIO "" (readFile servicefile) + writeservicefile :: Property Linux writeservicefile = property servicefile $ makeChange $ do c <- servicefilecontent File.viaStableTmp (\t -> writeFile t c) servicefile + setupservicefile :: Property Linux setupservicefile = check (not <$> goodservicefile) $ -- if it's running, it has the wrong configuration, -- so stop it@@ -275,8 +296,12 @@ `requires` daemonReloaded `requires` writeservicefile - setup = started service `requires` setupservicefile `requires` machined+ setup :: Property Linux+ setup = started service+ `requires` setupservicefile+ `requires` machined + teardown :: Property Linux teardown = check (doesFileExist servicefile) $ disabled service `requires` stopped service @@ -290,11 +315,12 @@ -- -- This uses nsenter to enter the container, by looking up the pid of the -- container's init process and using its namespace.-enterScript :: Container -> RevertableProperty NoInfo-enterScript c@(Container name _ _) = setup <!> teardown+enterScript :: Container -> RevertableProperty Linux Linux+enterScript c@(Container name _ _) =+ tightenTargets setup <!> tightenTargets teardown where- setup = combineProperties ("generated " ++ enterScriptFile c)- [ scriptfile `File.hasContent`+ setup = combineProperties ("generated " ++ enterScriptFile c) $ props+ & scriptfile `File.hasContent` [ "#!/usr/bin/perl" , "# Generated by propellor" , "my $pid=`machinectl show " ++ shellEscape name ++ " -p Leader | cut -d= -f2`;"@@ -309,8 +335,7 @@ , "}" , "exit(1);" ]- , scriptfile `File.mode` combineModes (readModes ++ executeModes)- ]+ & scriptfile `File.mode` combineModes (readModes ++ executeModes) teardown = File.notPresent scriptfile scriptfile = enterScriptFile c @@ -336,11 +361,14 @@ -- When there is no leading dash, "--" is prepended to the parameter. -- -- Reverting the property will remove a parameter, if it's present.-containerCfg :: String -> RevertableProperty HasInfo+containerCfg :: String -> RevertableProperty (HasInfo + Linux) (HasInfo + Linux) containerCfg p = RevertableProperty (mk True) (mk False) where- mk b = pureInfoProperty ("container configuration " ++ (if b then "" else "without ") ++ p') $- mempty { _chrootCfg = SystemdNspawnCfg [(p', b)] }+ mk b = tightenTargets $+ pureInfoProperty desc $+ mempty { _chrootCfg = SystemdNspawnCfg [(p', b)] }+ where+ desc = "container configuration " ++ (if b then "" else "without ") ++ p' p' = case p of ('-':_) -> p _ -> "--" ++ p@@ -348,18 +376,18 @@ -- | Bind mounts </etc/resolv.conf> from the host into the container. -- -- This property is enabled by default. Revert it to disable it.-resolvConfed :: RevertableProperty HasInfo+resolvConfed :: RevertableProperty (HasInfo + Linux) (HasInfo + Linux) resolvConfed = containerCfg "bind=/etc/resolv.conf" -- | Link the container's journal to the host's if possible. -- (Only works if the host has persistent journal enabled.) -- -- This property is enabled by default. Revert it to disable it.-linkJournal :: RevertableProperty HasInfo+linkJournal :: RevertableProperty (HasInfo + Linux) (HasInfo + Linux) linkJournal = containerCfg "link-journal=try-guest" -- | Disconnect networking of the container from the host.-privateNetwork :: RevertableProperty HasInfo+privateNetwork :: RevertableProperty (HasInfo + Linux) (HasInfo + Linux) privateNetwork = containerCfg "private-network" class Publishable a where@@ -397,7 +425,7 @@ -- > & Systemd.running Systemd.networkd -- > & Systemd.publish (Port 80 ->- Port 8080) -- > & Apt.installedRunning "apache2"-publish :: Publishable p => p -> RevertableProperty HasInfo+publish :: Publishable p => p -> RevertableProperty (HasInfo + Linux) (HasInfo + Linux) publish p = containerCfg $ "--port=" ++ toPublish p class Bindable a where@@ -410,9 +438,9 @@ toBind v = hostSide v ++ ":" ++ containerSide v -- | Bind mount a file or directory from the host into the container.-bind :: Bindable p => p -> RevertableProperty HasInfo+bind :: Bindable p => p -> RevertableProperty (HasInfo + Linux) (HasInfo + Linux) bind p = containerCfg $ "--bind=" ++ toBind p -- | Read-only mind mount.-bindRo :: Bindable p => p -> RevertableProperty HasInfo+bindRo :: Bindable p => p -> RevertableProperty (HasInfo + Linux) (HasInfo + Linux) bindRo p = containerCfg $ "--bind-ro=" ++ toBind p
src/Propellor/Property/Systemd/Core.hs view
@@ -6,5 +6,5 @@ -- dbus is only a Recommends of systemd, but is needed for communication -- from the systemd inside a container to the one outside, so make sure it -- gets installed.-installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.installed ["systemd", "dbus"]
src/Propellor/Property/Tor.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE TypeFamilies #-}+ module Propellor.Property.Tor where import Propellor.Base@@ -19,7 +21,7 @@ -- | Sets up a tor bridge. (Not a relay or exit node.) -- -- Uses port 443-isBridge :: Property NoInfo+isBridge :: Property DebianLike isBridge = configured [ ("BridgeRelay", "1") , ("Exitpolicy", "reject *:*")@@ -31,7 +33,7 @@ -- | Sets up a tor relay. -- -- Uses port 443-isRelay :: Property NoInfo+isRelay :: Property DebianLike isRelay = configured [ ("BridgeRelay", "0") , ("Exitpolicy", "reject *:*")@@ -44,21 +46,21 @@ -- -- This can be moved to a different IP without needing to wait to -- accumulate trust.-named :: NodeName -> Property HasInfo+named :: NodeName -> Property (HasInfo + DebianLike) named n = configured [("Nickname", n')] `describe` ("tor node named " ++ n') `requires` torPrivKey (Context ("tor " ++ n)) where n' = saneNickname n -torPrivKey :: Context -> Property HasInfo+torPrivKey :: Context -> Property (HasInfo + DebianLike) torPrivKey context = f `File.hasPrivContent` context `onChange` File.ownerGroup f user (userGroup user) `requires` torPrivKeyDirExists where f = torPrivKeyDir </> "secret_id_key" -torPrivKeyDirExists :: Property NoInfo+torPrivKeyDirExists :: Property DebianLike torPrivKeyDirExists = File.dirExists torPrivKeyDir `onChange` setperms `requires` installed@@ -71,20 +73,20 @@ -- | A tor server (bridge, relay, or exit) -- Don't use if you just want to run tor for personal use.-server :: Property NoInfo+server :: Property DebianLike server = configured [("SocksPort", "0")] `requires` installed `requires` Apt.installed ["ntp"] `describe` "tor server" -installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.installed ["tor"] -- | Specifies configuration settings. Any lines in the config file -- that set other values for the specified settings will be removed, -- while other settings are left as-is. Tor is restarted when -- configuration is changed.-configured :: [(String, String)] -> Property NoInfo+configured :: [(String, String)] -> Property DebianLike configured settings = File.fileProperty "tor configured" go mainConfig `onChange` restarted where@@ -105,19 +107,19 @@ -- -- For example, PerSecond "30 kibibytes" is the minimum limit -- for a useful relay.-bandwidthRate :: BwLimit -> Property NoInfo+bandwidthRate :: BwLimit -> Property DebianLike bandwidthRate (PerSecond s) = bandwidthRate' s 1 bandwidthRate (PerDay s) = bandwidthRate' s (24*60*60) bandwidthRate (PerMonth s) = bandwidthRate' s (31*24*60*60) -bandwidthRate' :: String -> Integer -> Property NoInfo+bandwidthRate' :: String -> Integer -> Property DebianLike bandwidthRate' s divby = case readSize dataUnits s of Just sz -> let v = show (sz `div` divby) ++ " bytes" in configured [("BandwidthRate", v)] `describe` ("tor BandwidthRate " ++ v) Nothing -> property ("unable to parse " ++ s) noChange -hiddenServiceAvailable :: HiddenServiceName -> Int -> Property NoInfo+hiddenServiceAvailable :: HiddenServiceName -> Int -> Property DebianLike hiddenServiceAvailable hn port = hiddenServiceHostName $ hiddenService hn port where hiddenServiceHostName p = adjustPropertySatisfy p $ \satisfy -> do@@ -126,7 +128,7 @@ warningMessage $ unwords ["hidden service hostname:", h] return r -hiddenService :: HiddenServiceName -> Int -> Property NoInfo+hiddenService :: HiddenServiceName -> Int -> Property DebianLike hiddenService hn port = ConfFile.adjustSection (unwords ["hidden service", hn, "available on port", show port]) (== oniondir)@@ -139,18 +141,18 @@ oniondir = unwords ["HiddenServiceDir", varLib </> hn] onionport = unwords ["HiddenServicePort", show port, "127.0.0.1:" ++ show port] -hiddenServiceData :: IsContext c => HiddenServiceName -> c -> Property HasInfo-hiddenServiceData hn context = combineProperties desc- [ installonion "hostname"- , installonion "private_key"- ]+hiddenServiceData :: IsContext c => HiddenServiceName -> c -> Property (HasInfo + DebianLike)+hiddenServiceData hn context = combineProperties desc $ props+ & installonion "hostname"+ & installonion "private_key" where desc = unwords ["hidden service data available in", varLib </> hn]+ installonion :: FilePath -> Property (HasInfo + DebianLike) installonion f = withPrivData (PrivFile $ varLib </> hn </> f) context $ \getcontent ->- property desc $ getcontent $ install $ varLib </> hn </> f- install f privcontent = ifM (liftIO $ doesFileExist f)+ property' desc $ \w -> getcontent $ install w $ varLib </> hn </> f+ install w f privcontent = ifM (liftIO $ doesFileExist f) ( noChange- , ensureProperties+ , ensureProperty w $ propertyList desc $ toProps [ property desc $ makeChange $ do createDirectoryIfMissing True (takeDirectory f) writeFileProtected f (unlines (privDataLines privcontent))@@ -161,7 +163,7 @@ ] ) -restarted :: Property NoInfo+restarted :: Property DebianLike restarted = Service.restarted "tor" mainConfig :: FilePath
src/Propellor/Property/Unbound.hs view
@@ -41,13 +41,13 @@ type ZoneType = String -installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.installed ["unbound"] -restarted :: Property NoInfo+restarted :: Property DebianLike restarted = Service.restarted "unbound" -reloaded :: Property NoInfo+reloaded :: Property DebianLike reloaded = Service.reloaded "unbound" dValue :: BindDomain -> String@@ -90,7 +90,7 @@ -- > , (AbsDomain "myrouter.example.com", PTR $ reverseIP $ IPv4 "192.168.1.1") -- > , (AbsDomain "mylaptop.example.com", PTR $ reverseIP $ IPv4 "192.168.1.2") -- > ]-cachingDnsServer :: [UnboundSection] -> [UnboundZone] -> [UnboundHost] -> Property NoInfo+cachingDnsServer :: [UnboundSection] -> [UnboundZone] -> [UnboundHost] -> Property DebianLike cachingDnsServer sections zones hosts = config `hasContent` (comment : otherSections ++ serverSection) `onChange` restarted
src/Propellor/Property/User.hs view
@@ -7,8 +7,8 @@ data Eep = YesReallyDeleteHome -accountFor :: User -> Property NoInfo-accountFor user@(User u) = check nohomedir go+accountFor :: User -> Property DebianLike+accountFor user@(User u) = tightenTargets $ check nohomedir go `describe` ("account for " ++ u) where nohomedir = isNothing <$> catchMaybeIO (homedir user)@@ -18,11 +18,11 @@ , u ] -systemAccountFor :: User -> Property NoInfo+systemAccountFor :: User -> Property DebianLike systemAccountFor user@(User u) = systemAccountFor' user Nothing (Just (Group u)) -systemAccountFor' :: User -> Maybe FilePath -> Maybe Group -> Property NoInfo-systemAccountFor' (User u) mhome mgroup = check nouser go+systemAccountFor' :: User -> Maybe FilePath -> Maybe Group -> Property DebianLike+systemAccountFor' (User u) mhome mgroup = tightenTargets $ check nouser go `describe` ("system account for " ++ u) where nouser = isNothing <$> catchMaybeIO (getUserEntryForName u)@@ -43,8 +43,8 @@ ] -- | Removes user home directory!! Use with caution.-nuked :: User -> Eep -> Property NoInfo-nuked user@(User u) _ = check hashomedir go+nuked :: User -> Eep -> Property DebianLike+nuked user@(User u) _ = tightenTargets $ check hashomedir go `describe` ("nuked user " ++ u) where hashomedir = isJust <$> catchMaybeIO (homedir user)@@ -55,13 +55,13 @@ -- | Only ensures that the user has some password set. It may or may -- not be a password from the PrivData.-hasSomePassword :: User -> Property HasInfo+hasSomePassword :: User -> Property (HasInfo + DebianLike) hasSomePassword user = hasSomePassword' user hostContext -- | While hasSomePassword uses the name of the host as context, -- this allows specifying a different context. This is useful when -- you want to use the same password on multiple hosts, for example.-hasSomePassword' :: IsContext c => User -> c -> Property HasInfo+hasSomePassword' :: IsContext c => User -> c -> Property (HasInfo + DebianLike) hasSomePassword' user context = check ((/= HasPassword) <$> getPasswordStatus user) $ hasPassword' user context @@ -71,12 +71,14 @@ -- A user's password can be stored in the PrivData in either of two forms; -- the full cleartext <Password> or a <CryptPassword> hash. The latter -- is obviously more secure.-hasPassword :: User -> Property HasInfo+hasPassword :: User -> Property (HasInfo + DebianLike) hasPassword user = hasPassword' user hostContext -hasPassword' :: IsContext c => User -> c -> Property HasInfo-hasPassword' (User u) context = go `requires` shadowConfig True+hasPassword' :: IsContext c => User -> c -> Property (HasInfo + DebianLike)+hasPassword' (User u) context = go+ `requires` shadowConfig True where+ go :: Property (HasInfo + UnixLike) go = withSomePrivData srcs context $ property (u ++ " has password") . setPassword srcs =@@ -94,7 +96,7 @@ -- | 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 :: User -> String -> Property DebianLike hasInsecurePassword u@(User n) p = property (n ++ " has insecure password") $ chpasswd u p [] @@ -104,9 +106,10 @@ hPutStrLn h $ user ++ ":" ++ v hClose h -lockedPassword :: User -> Property NoInfo-lockedPassword user@(User u) = check (not <$> isLockedPassword user) go- `describe` ("locked " ++ u ++ " password")+lockedPassword :: User -> Property DebianLike+lockedPassword user@(User u) = tightenTargets $ + check (not <$> isLockedPassword user) go+ `describe` ("locked " ++ u ++ " password") where go = cmdProperty "passwd" [ "--lock"@@ -130,8 +133,8 @@ homedir :: User -> IO FilePath homedir (User user) = homeDirectory <$> getUserEntryForName user -hasGroup :: User -> Group -> Property NoInfo-hasGroup (User user) (Group group') = check test go+hasGroup :: User -> Group -> Property DebianLike+hasGroup (User user) (Group group') = tightenTargets $ check test go `describe` unwords ["user", user, "in group", group'] where test = not . elem group' . words <$> readProcess "groups" [user]@@ -145,12 +148,13 @@ -- -- Note that some groups may only exit after installation of other -- software. When a group does not exist yet, the user won't be added to it.-hasDesktopGroups :: User -> Property NoInfo-hasDesktopGroups user@(User u) = property desc $ do+hasDesktopGroups :: User -> Property DebianLike+hasDesktopGroups user@(User u) = property' desc $ \o -> do existinggroups <- map (fst . break (== ':')) . lines <$> liftIO (readFile "/etc/group") let toadd = filter (`elem` existinggroups) desktopgroups- ensureProperty $ propertyList desc $ map (hasGroup user . Group) toadd+ ensureProperty o $ propertyList desc $ toProps $+ map (hasGroup user . Group) toadd where desc = "user " ++ u ++ " is in standard desktop groups" -- This list comes from user-setup's debconf@@ -170,11 +174,11 @@ ] -- | Controls whether shadow passwords are enabled or not.-shadowConfig :: Bool -> Property NoInfo-shadowConfig True = check (not <$> shadowExists)+shadowConfig :: Bool -> Property DebianLike+shadowConfig True = tightenTargets $ check (not <$> shadowExists) (cmdProperty "shadowconfig" ["on"]) `describe` "shadow passwords enabled"-shadowConfig False = check shadowExists+shadowConfig False = tightenTargets $ check shadowExists (cmdProperty "shadowconfig" ["off"]) `describe` "shadow passwords disabled" @@ -183,11 +187,11 @@ -- | Ensures that a user has a specified login shell, and that the shell -- is enabled in /etc/shells.-hasLoginShell :: User -> FilePath -> Property NoInfo+hasLoginShell :: User -> FilePath -> Property DebianLike hasLoginShell user loginshell = shellSetTo user loginshell `requires` shellEnabled loginshell -shellSetTo :: User -> FilePath -> Property NoInfo-shellSetTo (User u) loginshell = check needchangeshell+shellSetTo :: User -> FilePath -> Property DebianLike+shellSetTo (User u) loginshell = tightenTargets $ check needchangeshell (cmdProperty "chsh" ["--shell", loginshell, u]) `describe` (u ++ " has login shell " ++ loginshell) where@@ -196,5 +200,6 @@ return (currshell /= loginshell) -- | Ensures that /etc/shells contains a shell.-shellEnabled :: FilePath -> Property NoInfo-shellEnabled loginshell = "/etc/shells" `File.containsLine` loginshell+shellEnabled :: FilePath -> Property DebianLike+shellEnabled loginshell = tightenTargets $+ "/etc/shells" `File.containsLine` loginshell
src/Propellor/Property/Uwsgi.hs view
@@ -11,7 +11,7 @@ type AppName = String -appEnabled :: AppName -> ConfigFile -> RevertableProperty NoInfo+appEnabled :: AppName -> ConfigFile -> RevertableProperty DebianLike DebianLike appEnabled an cf = enable <!> disable where enable = appVal an `File.isSymlinkedTo` appValRelativeCfg an@@ -24,9 +24,9 @@ `requires` installed `onChange` reloaded -appAvailable :: AppName -> ConfigFile -> Property NoInfo+appAvailable :: AppName -> ConfigFile -> Property DebianLike appAvailable an cf = ("uwsgi app available " ++ an) ==>- appCfg an `File.hasContent` (comment : cf)+ tightenTargets (appCfg an `File.hasContent` (comment : cf)) where comment = "# deployed with propellor, do not modify" @@ -39,11 +39,11 @@ appValRelativeCfg :: AppName -> File.LinkTarget appValRelativeCfg an = File.LinkTarget $ "../apps-available" </> an <.> "ini" -installed :: Property NoInfo+installed :: Property DebianLike installed = Apt.installed ["uwsgi"] -restarted :: Property NoInfo+restarted :: Property DebianLike restarted = Service.restarted "uwsgi" -reloaded :: Property NoInfo+reloaded :: Property DebianLike reloaded = Service.reloaded "uwsgi"
src/Propellor/Property/ZFS/Properties.hs view
@@ -3,6 +3,7 @@ -- Functions defining zfs Properties. module Propellor.Property.ZFS.Properties (+ ZFSOS, zfsExists, zfsSetProperties ) where@@ -11,9 +12,12 @@ import Data.List (intercalate) import qualified Propellor.Property.ZFS.Process as ZP +-- | OS's that support ZFS+type ZFSOS = Linux + FreeBSD+ -- | Will ensure that a ZFS volume exists with the specified mount point. -- This requires the pool to exist as well, but we don't create pools yet.-zfsExists :: ZFS -> Property NoInfo+zfsExists :: ZFS -> Property ZFSOS zfsExists z = check (not <$> ZP.zfsExists z) create `describe` unwords ["Creating", zfsName z] where@@ -21,16 +25,16 @@ create = cmdProperty p a -- | Sets the given properties. Returns True if all were successfully changed, False if not.-zfsSetProperties :: ZFS -> ZFSProperties -> Property NoInfo+zfsSetProperties :: ZFS -> ZFSProperties -> Property ZFSOS zfsSetProperties z setProperties = setall `requires` zfsExists z where spcmd :: String -> String -> (String, [String]) spcmd p v = ZP.zfsCommand "set" [Just (intercalate "=" [p, v]), Nothing] z - setprop :: (String, String) -> Property NoInfo+ setprop :: (String, String) -> Property ZFSOS setprop (p, v) = check (ZP.zfsExists z) $ cmdProperty (fst (spcmd p v)) (snd (spcmd p v)) setall = combineProperties (unwords ["Setting properties on", zfsName z]) $- map setprop $ toPropertyList setProperties+ toProps $ map setprop $ toPropertyList setProperties
src/Propellor/Spin.hs view
@@ -90,7 +90,7 @@ error "remote propellor failed" where hn = fromMaybe target relay- sys = case getInfo (hostInfo hst) of+ sys = case fromInfo (hostInfo hst) of InfoVal o -> Just o NoInfoVal -> Nothing @@ -98,7 +98,7 @@ viarelay = isJust relay && not relaying probecmd = intercalate " ; "- ["if [ ! -d " ++ localdir ++ "/.git ]"+ [ "if [ ! -d " ++ localdir ++ "/.git ]" , "then (" ++ intercalate " && " [ installGitCommand sys , "echo " ++ toMarked statusMarker (show NeedGitClone)@@ -170,7 +170,7 @@ return ip configips = map fromIPAddr $ mapMaybe getIPAddr $- S.toList $ fromDnsInfo $ getInfo $ hostInfo hst+ S.toList $ fromDnsInfo $ fromInfo $ hostInfo hst -- Update the privdata, repo url, and git repo over the ssh -- connection, talking to the user's local propellor instance which is
src/Propellor/Types.hs view
@@ -1,264 +1,156 @@-{-# LANGUAGE PackageImports #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE EmptyDataDecls #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveDataTypeable #-} -module Propellor.Types- ( Host(..)- , Property- , Info- , HasInfo- , NoInfo- , CInfo+module Propellor.Types (+ -- * Core data types+ Host(..)+ , Property(..)+ , property , Desc- , infoProperty- , simpleProperty- , adjustPropertySatisfy- , propertyInfo- , propertyDesc- , propertyChildren , RevertableProperty(..)- , MkRevertableProperty(..)- , IsProp(..)+ , (<!>)+ , Propellor(..)+ , LiftPropellor(..)+ , Info+ -- * Types of properties+ , UnixLike+ , Linux+ , DebianLike+ , Debian+ , Buntish+ , FreeBSD+ , HasInfo+ , type (+)+ , TightenTargets(..)+ -- * Combining and modifying properties , Combines(..) , CombinedType , ResultCombiner- , Propellor(..)- , LiftPropellor(..)- , EndAction(..)+ , adjustPropertySatisfy+ -- * Other included types , module Propellor.Types.OS , module Propellor.Types.Dns , module Propellor.Types.Result , module Propellor.Types.ZFS- , propertySatisfy- , ignoreInfo ) where import Data.Monoid-import "mtl" Control.Monad.RWS.Strict-import Control.Monad.Catch-import Data.Typeable-import Control.Applicative-import Prelude +import Propellor.Types.Core import Propellor.Types.Info import Propellor.Types.OS import Propellor.Types.Dns import Propellor.Types.Result+import Propellor.Types.MetaTypes import Propellor.Types.ZFS --- | Everything Propellor knows about a system: Its hostname,--- properties and their collected info.-data Host = Host- { hostName :: HostName- , hostProperties :: [Property HasInfo]- , hostInfo :: Info- }- deriving (Show, Typeable)---- | Propellor's monad provides read-only access to info about the host--- it's running on, and a writer to accumulate EndActions.-newtype Propellor p = Propellor { runWithHost :: RWST Host [EndAction] () IO p }- deriving- ( Monad- , Functor- , Applicative- , MonadReader Host- , MonadWriter [EndAction]- , MonadIO- , MonadCatch- , 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- -- | The second action is only run if the first action does not fail.- mappend x y = do- rx <- x- case rx of- FailedChange -> return FailedChange- _ -> do- ry <- y- return (rx <> ry)---- | An action that Propellor runs at the end, after trying to satisfy all--- properties. It's passed the combined Result of the entire Propellor run.-data EndAction = EndAction Desc (Result -> Propellor Result)--type Desc = String- -- | The core data type of Propellor, this represents a property--- that the system should have, and an action to ensure it has the--- property.+-- that the system should have, with a descrition, and an action to ensure+-- it has the property.+-- that have the property. ----- A property can have associated `Info` or not. This is tracked at the--- type level with Property `NoInfo` and Property `HasInfo`.+-- There are different types of properties that target different OS's,+-- and so have different metatypes. +-- For example: "Property DebianLike" and "Property FreeBSD". ----- There are many instances and type families, which are mostly used+-- Also, some properties have associated `Info`, which is indicated in+-- their type: "Property (HasInfo + DebianLike)"+--+-- There are many associated type families, which are mostly used -- internally, so you needn't worry about them.-data Property i where- IProperty :: Desc -> Propellor Result -> Info -> [Property HasInfo] -> Property HasInfo- SProperty :: Desc -> Propellor Result -> [Property NoInfo] -> Property NoInfo---- | Indicates that a Property has associated Info.-data HasInfo--- | Indicates that a Property does not have Info.-data NoInfo---- | Type level calculation of the combination of HasInfo and/or NoInfo-type family CInfo x y-type instance CInfo HasInfo HasInfo = HasInfo-type instance CInfo HasInfo NoInfo = HasInfo-type instance CInfo NoInfo HasInfo = HasInfo-type instance CInfo NoInfo NoInfo = NoInfo---- | Constructs a Property with associated Info.-infoProperty- :: Desc -- ^ description of the property- -> Propellor Result -- ^ action to run to satisfy the property (must be idempotent; may run repeatedly)- -> Info -- ^ info associated with the property- -> [Property i] -- ^ child properties- -> Property HasInfo-infoProperty d a i cs = IProperty d a i (map toIProperty cs)---- | Constructs a Property with no Info.-simpleProperty :: Desc -> Propellor Result -> [Property NoInfo] -> Property NoInfo-simpleProperty = SProperty--toIProperty :: Property i -> Property HasInfo-toIProperty p@(IProperty {}) = p-toIProperty (SProperty d s cs) = IProperty d s mempty (map toIProperty cs)--toSProperty :: Property i -> Property NoInfo-toSProperty (IProperty d s _ cs) = SProperty d s (map toSProperty cs)-toSProperty p@(SProperty {}) = p+data Property metatypes = Property metatypes Desc (Propellor Result) Info [ChildProperty] --- | Makes a version of a Proprty without its Info.--- Use with caution!-ignoreInfo :: Property i -> Property NoInfo-ignoreInfo = toSProperty+instance Show (Property metatypes) where+ show p = "property " ++ show (getDesc p) --- | Gets the action that can be run to satisfy a Property.--- You should never run this action directly. Use--- 'Propellor.Engine.ensureProperty` instead.-propertySatisfy :: Property i -> Propellor Result-propertySatisfy (IProperty _ a _ _) = a-propertySatisfy (SProperty _ a _) = a+-- | Constructs a Property, from a description and an action to run to+-- ensure the Property is met.+--+-- Due to the polymorphic return type of this function, most uses will need+-- to specify a type signature. This lets you specify what OS the property+-- targets, etc.+--+-- For example:+--+-- > foo :: Property Debian+-- > foo = property "foo" $ do+-- > ...+-- > return MadeChange+property+ :: SingI metatypes+ => Desc+ -> Propellor Result+ -> Property (MetaTypes metatypes)+property d a = Property sing d a mempty mempty -- | Changes the action that is performed to satisfy a property.-adjustPropertySatisfy :: Property i -> (Propellor Result -> Propellor Result) -> Property i-adjustPropertySatisfy (IProperty d s i cs) f = IProperty d (f s) i cs-adjustPropertySatisfy (SProperty d s cs) f = SProperty d (f s) cs--propertyInfo :: Property i -> Info-propertyInfo (IProperty _ _ i _) = i-propertyInfo (SProperty {}) = mempty--propertyDesc :: Property i -> Desc-propertyDesc (IProperty d _ _ _) = d-propertyDesc (SProperty d _ _) = d--instance Show (Property i) where- show p = "property " ++ show (propertyDesc p)---- | A Property can include a list of child properties that it also--- satisfies. This allows them to be introspected to collect their info, etc.-propertyChildren :: Property i -> [Property i]-propertyChildren (IProperty _ _ _ cs) = cs-propertyChildren (SProperty _ _ cs) = cs+adjustPropertySatisfy :: Property metatypes -> (Propellor Result -> Propellor Result) -> Property metatypes+adjustPropertySatisfy (Property t d s i c) f = Property t d (f s) i c -- | A property that can be reverted. The first Property is run -- normally and the second is run when it's reverted.-data RevertableProperty i = RevertableProperty- { setupRevertableProperty :: Property i- , undoRevertableProperty :: Property i+data RevertableProperty setupmetatypes undometatypes = RevertableProperty+ { setupRevertableProperty :: Property setupmetatypes+ , undoRevertableProperty :: Property undometatypes } -instance Show (RevertableProperty i) where+instance Show (RevertableProperty setupmetatypes undometatypes) where show (RevertableProperty p _) = show p -class MkRevertableProperty i1 i2 where- -- | Shorthand to construct a revertable property.- (<!>) :: Property i1 -> Property i2 -> RevertableProperty (CInfo i1 i2)--instance MkRevertableProperty HasInfo HasInfo where- x <!> y = RevertableProperty x y-instance MkRevertableProperty NoInfo NoInfo where- x <!> y = RevertableProperty x y-instance MkRevertableProperty NoInfo HasInfo where- x <!> y = RevertableProperty (toProp x) y-instance MkRevertableProperty HasInfo NoInfo where- x <!> y = RevertableProperty x (toProp y)---- | Class of types that can be used as properties of a host.-class IsProp p where- setDesc :: p -> Desc -> p- toProp :: p -> Property HasInfo- getDesc :: p -> Desc- -- | Gets the info of the property, combined with all info- -- of all children properties.- getInfoRecursive :: p -> Info+-- | Shorthand to construct a revertable property from any two Properties.+(<!>)+ :: Property setupmetatypes+ -> Property undometatypes+ -> RevertableProperty setupmetatypes undometatypes+setup <!> undo = RevertableProperty setup undo -instance IsProp (Property HasInfo) where- setDesc (IProperty _ a i cs) d = IProperty d a i cs- toProp = id- getDesc = propertyDesc- getInfoRecursive (IProperty _ _ i cs) =- i <> mconcat (map getInfoRecursive cs)-instance IsProp (Property NoInfo) where- setDesc (SProperty _ a cs) d = SProperty d a cs- toProp = toIProperty- getDesc = propertyDesc- getInfoRecursive _ = mempty+instance IsProp (Property metatypes) where+ setDesc (Property t _ a i c) d = Property t d a i c+ getDesc (Property _ d _ _ _) = d+ getChildren (Property _ _ _ _ c) = c+ addChildren (Property t d a i c) c' = Property t d a i (c ++ c')+ getInfoRecursive (Property _ _ _ i c) =+ i <> mconcat (map getInfoRecursive c)+ getInfo (Property _ _ _ i _) = i+ toChildProperty (Property _ d a i c) = ChildProperty d a i c+ getSatisfy (Property _ _ a _ _) = a -instance IsProp (RevertableProperty HasInfo) where- setDesc = setDescR+instance IsProp (RevertableProperty setupmetatypes undometatypes) where+ -- | Sets the description of both sides.+ setDesc (RevertableProperty p1 p2) d =+ RevertableProperty (setDesc p1 d) (setDesc p2 ("not " ++ d)) getDesc (RevertableProperty p1 _) = getDesc p1- toProp (RevertableProperty p1 _) = p1+ getChildren (RevertableProperty p1 _) = getChildren p1+ -- | Only add children to the active side.+ addChildren (RevertableProperty p1 p2) c = RevertableProperty (addChildren p1 c) p2 -- | Return the Info of the currently active side. getInfoRecursive (RevertableProperty p1 _p2) = getInfoRecursive p1-instance IsProp (RevertableProperty NoInfo) where- setDesc = setDescR- getDesc (RevertableProperty p1 _) = getDesc p1- toProp (RevertableProperty p1 _) = toProp p1- getInfoRecursive (RevertableProperty _ _) = mempty---- | Sets the description of both sides.-setDescR :: IsProp (Property i) => RevertableProperty i -> Desc -> RevertableProperty i-setDescR (RevertableProperty p1 p2) d =- RevertableProperty (setDesc p1 d) (setDesc p2 ("not " ++ d))+ getInfo (RevertableProperty p1 _p2) = getInfo p1+ toChildProperty (RevertableProperty p1 _p2) = toChildProperty p1+ getSatisfy (RevertableProperty p1 _) = getSatisfy p1 -- | Type level calculation of the type that results from combining two -- types of properties. type family CombinedType x y-type instance CombinedType (Property x) (Property y) = Property (CInfo x y)-type instance CombinedType (RevertableProperty x) (RevertableProperty y) = RevertableProperty (CInfo x y)+type instance CombinedType (Property (MetaTypes x)) (Property (MetaTypes y)) = Property (MetaTypes (Combine x y))+type instance CombinedType (RevertableProperty (MetaTypes x) (MetaTypes x')) (RevertableProperty (MetaTypes y) (MetaTypes y')) = RevertableProperty (MetaTypes (Combine x y)) (MetaTypes (Combine x' y')) -- When only one of the properties is revertable, the combined property is -- not fully revertable, so is not a RevertableProperty.-type instance CombinedType (RevertableProperty x) (Property y) = Property (CInfo x y)-type instance CombinedType (Property x) (RevertableProperty y) = Property (CInfo x y)+type instance CombinedType (RevertableProperty (MetaTypes x) (MetaTypes x')) (Property (MetaTypes y)) = Property (MetaTypes (Combine x y))+type instance CombinedType (Property (MetaTypes x)) (RevertableProperty (MetaTypes y) (MetaTypes y')) = Property (MetaTypes (Combine x y)) type ResultCombiner = Propellor Result -> Propellor Result -> Propellor Result class Combines x y where -- | Combines together two properties, yielding a property that- -- has the description and info of the first, and that has the second- -- property as a child.+ -- has the description and info of the first, and that has the+ -- second property as a child property. combineWith :: ResultCombiner -- ^ How to combine the actions to satisfy the properties.@@ -269,73 +161,37 @@ -> y -> CombinedType x y -instance Combines (Property HasInfo) (Property HasInfo) where- combineWith f _ (IProperty d1 a1 i1 cs1) y@(IProperty _d2 a2 _i2 _cs2) =- IProperty d1 (f a1 a2) i1 (y : cs1)--instance Combines (Property HasInfo) (Property NoInfo) where- combineWith f _ (IProperty d1 a1 i1 cs1) y@(SProperty _d2 a2 _cs2) =- IProperty d1 (f a1 a2) i1 (toIProperty y : cs1)--instance Combines (Property NoInfo) (Property HasInfo) where- combineWith f _ (SProperty d1 a1 cs1) y@(IProperty _d2 a2 _i2 _cs2) =- IProperty d1 (f a1 a2) mempty (y : map toIProperty cs1)--instance Combines (Property NoInfo) (Property NoInfo) where- combineWith f _ (SProperty d1 a1 cs1) y@(SProperty _d2 a2 _cs2) =- SProperty d1 (f a1 a2) (y : cs1)--instance Combines (RevertableProperty NoInfo) (RevertableProperty NoInfo) where- combineWith = combineWithRR-instance Combines (RevertableProperty HasInfo) (RevertableProperty HasInfo) where- combineWith = combineWithRR-instance Combines (RevertableProperty HasInfo) (RevertableProperty NoInfo) where- combineWith = combineWithRR-instance Combines (RevertableProperty NoInfo) (RevertableProperty HasInfo) where- combineWith = combineWithRR-instance Combines (RevertableProperty NoInfo) (Property HasInfo) where- combineWith = combineWithRP-instance Combines (RevertableProperty NoInfo) (Property NoInfo) where- combineWith = combineWithRP-instance Combines (RevertableProperty HasInfo) (Property HasInfo) where- combineWith = combineWithRP-instance Combines (RevertableProperty HasInfo) (Property NoInfo) where- combineWith = combineWithRP-instance Combines (Property HasInfo) (RevertableProperty NoInfo) where- combineWith = combineWithPR-instance Combines (Property NoInfo) (RevertableProperty NoInfo) where- combineWith = combineWithPR-instance Combines (Property HasInfo) (RevertableProperty HasInfo) where- combineWith = combineWithPR-instance Combines (Property NoInfo) (RevertableProperty HasInfo) where- combineWith = combineWithPR--combineWithRR- :: Combines (Property x) (Property y)- => ResultCombiner- -> ResultCombiner- -> RevertableProperty x- -> RevertableProperty y- -> RevertableProperty (CInfo x y)-combineWithRR sf tf (RevertableProperty s1 t1) (RevertableProperty s2 t2) =- RevertableProperty- (combineWith sf tf s1 s2)- (combineWith tf sf t1 t2)+instance (CheckCombinable x y ~ 'CanCombine, SingI (Combine x y)) => Combines (Property (MetaTypes x)) (Property (MetaTypes y)) where+ combineWith f _ (Property _ d1 a1 i1 c1) (Property _ d2 a2 i2 c2) =+ Property sing d1 (f a1 a2) i1 (ChildProperty d2 a2 i2 c2 : c1)+instance (CheckCombinable x y ~ 'CanCombine, CheckCombinable x' y' ~ 'CanCombine, SingI (Combine x y), SingI (Combine x' y')) => Combines (RevertableProperty (MetaTypes x) (MetaTypes x')) (RevertableProperty (MetaTypes y) (MetaTypes y')) where+ combineWith sf tf (RevertableProperty s1 t1) (RevertableProperty s2 t2) =+ RevertableProperty+ (combineWith sf tf s1 s2)+ (combineWith tf sf t1 t2)+instance (CheckCombinable x y ~ 'CanCombine, SingI (Combine x y)) => Combines (RevertableProperty (MetaTypes x) (MetaTypes x')) (Property (MetaTypes y)) where+ combineWith sf tf (RevertableProperty x _) y = combineWith sf tf x y+instance (CheckCombinable x y ~ 'CanCombine, SingI (Combine x y)) => Combines (Property (MetaTypes x)) (RevertableProperty (MetaTypes y) (MetaTypes y')) where+ combineWith sf tf x (RevertableProperty y _) = combineWith sf tf x y -combineWithRP- :: Combines (Property i) y- => (Propellor Result -> Propellor Result -> Propellor Result)- -> (Propellor Result -> Propellor Result -> Propellor Result)- -> RevertableProperty i- -> y- -> CombinedType (Property i) y-combineWithRP sf tf (RevertableProperty x _) y = combineWith sf tf x y+class TightenTargets p where+ -- | Tightens the MetaType list of a Property (or similar),+ -- to contain fewer targets.+ --+ -- For example, to make a property that uses apt-get, which is only+ -- available on DebianLike systems:+ --+ -- > upgraded :: Property DebianLike+ -- > upgraded = tightenTargets $ cmdProperty "apt-get" ["upgrade"]+ tightenTargets+ :: + -- Note that this uses PolyKinds+ ( (Targets untightened `NotSuperset` Targets tightened) ~ 'CanCombine+ , (NonTargets tightened `NotSuperset` NonTargets untightened) ~ 'CanCombine+ , SingI tightened+ )+ => p (MetaTypes untightened)+ -> p (MetaTypes tightened) -combineWithPR- :: Combines x (Property i)- => (Propellor Result -> Propellor Result -> Propellor Result)- -> (Propellor Result -> Propellor Result -> Propellor Result)- -> x- -> RevertableProperty i- -> CombinedType x (Property i)-combineWithPR sf tf x (RevertableProperty y _) = combineWith sf tf x y+instance TightenTargets Property where+ tightenTargets (Property _ d a i c) = Property sing d a i c
src/Propellor/Types/CmdLine.hs view
@@ -28,4 +28,5 @@ | ChrootChain HostName FilePath Bool Bool | GitPush Fd Fd | Check+ | Init deriving (Read, Show, Eq)
+ src/Propellor/Types/Core.hs view
@@ -0,0 +1,106 @@+{-# LANGUAGE PackageImports #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}++module Propellor.Types.Core where++import Propellor.Types.Info+import Propellor.Types.OS+import Propellor.Types.Result++import Data.Monoid+import "mtl" Control.Monad.RWS.Strict+import Control.Monad.Catch+import Control.Applicative+import Prelude++-- | Everything Propellor knows about a system: Its hostname,+-- properties and their collected info.+data Host = Host+ { hostName :: HostName+ , hostProperties :: [ChildProperty]+ , hostInfo :: Info+ }+ deriving (Show, Typeable)++-- | Propellor's monad provides read-only access to info about the host+-- it's running on, and a writer to accumulate EndActions.+newtype Propellor p = Propellor { runWithHost :: RWST Host [EndAction] () IO p }+ deriving+ ( Monad+ , Functor+ , Applicative+ , MonadReader Host+ , MonadWriter [EndAction]+ , MonadIO+ , MonadCatch+ , 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+ -- | The second action is only run if the first action does not fail.+ mappend x y = do+ rx <- x+ case rx of+ FailedChange -> return FailedChange+ _ -> do+ ry <- y+ return (rx <> ry)++-- | An action that Propellor runs at the end, after trying to satisfy all+-- properties. It's passed the combined Result of the entire Propellor run.+data EndAction = EndAction Desc (Result -> Propellor Result)++type Desc = String++-- | Props is a combination of a list of properties, with their combined +-- metatypes.+data Props metatypes = Props [ChildProperty]++-- | Since there are many different types of Properties, they cannot be put+-- into a list. The simplified ChildProperty can be put into a list.+data ChildProperty = ChildProperty Desc (Propellor Result) Info [ChildProperty]+ +instance Show ChildProperty where+ show p = "property " ++ show (getDesc p)++class IsProp p where+ setDesc :: p -> Desc -> p+ getDesc :: p -> Desc+ getChildren :: p -> [ChildProperty]+ addChildren :: p -> [ChildProperty] -> p+ -- | Gets the info of the property, combined with all info+ -- of all children properties.+ getInfoRecursive :: p -> Info+ -- | Info, not including info from children.+ getInfo :: p -> Info+ -- | Gets a ChildProperty representing the Property.+ -- You should not normally need to use this.+ toChildProperty :: p -> ChildProperty+ -- | Gets the action that can be run to satisfy a Property.+ -- You should never run this action directly. Use+ -- 'Propellor.EnsureProperty.ensureProperty` instead.+ getSatisfy :: p -> Propellor Result++instance IsProp ChildProperty where+ setDesc (ChildProperty _ a i c) d = ChildProperty d a i c+ getDesc (ChildProperty d _ _ _) = d+ getChildren (ChildProperty _ _ _ c) = c+ addChildren (ChildProperty d a i c) c' = ChildProperty d a i (c ++ c')+ getInfoRecursive (ChildProperty _ _ i c) =+ i <> mconcat (map getInfoRecursive c)+ getInfo (ChildProperty _ _ i _) = i+ toChildProperty = id+ getSatisfy (ChildProperty _ a _ _) = a
src/Propellor/Types/Info.hs view
@@ -4,7 +4,8 @@ Info, IsInfo(..), addInfo,- getInfo,+ toInfo,+ fromInfo, mapInfo, propagatableInfo, InfoVal(..),@@ -18,6 +19,9 @@ import Prelude -- | Information about a Host, which can be provided by its properties.+--+-- Many different types of data can be contained in the same Info value+-- at the same time. See `toInfo` and `fromInfo`. newtype Info = Info [InfoEntry] deriving (Monoid, Show) @@ -46,9 +50,14 @@ addInfo :: IsInfo v => Info -> v -> Info addInfo (Info l) v = Info (InfoEntry v:l) +-- | Converts any value in the `IsInfo` type class into an Info,+-- which is otherwise empty.+toInfo :: IsInfo v => v -> Info+toInfo = addInfo mempty+ -- The list is reversed here because addInfo builds it up in reverse order.-getInfo :: IsInfo v => Info -> v-getInfo (Info l) = mconcat (mapMaybe extractInfoEntry (reverse l))+fromInfo :: IsInfo v => Info -> v+fromInfo (Info l) = mconcat (mapMaybe extractInfoEntry (reverse l)) -- | Maps a function over all values stored in the Info that are of the -- appropriate type.
+ src/Propellor/Types/MetaTypes.hs view
@@ -0,0 +1,213 @@+{-# LANGUAGE TypeOperators, PolyKinds, DataKinds, TypeFamilies, UndecidableInstances, FlexibleInstances, GADTs #-}++module Propellor.Types.MetaTypes (+ MetaType(..),+ UnixLike,+ Linux,+ DebianLike,+ Debian,+ Buntish,+ FreeBSD,+ HasInfo,+ MetaTypes,+ type (+),+ sing,+ SingI,+ IncludesInfo,+ Targets,+ NonTargets,+ NotSuperset,+ Combine,+ CheckCombine(..),+ CheckCombinable,+ type (&&),+ Not,+ EqT,+ Union,+) where++import Propellor.Types.Singletons+import Propellor.Types.OS++data MetaType+ = Targeting TargetOS -- ^ A target OS of a Property+ | WithInfo -- ^ Indicates that a Property has associated Info+ deriving (Show, Eq, Ord)++-- | Any unix-like system+type UnixLike = MetaTypes '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish, 'Targeting 'OSFreeBSD ]+-- | Any linux system+type Linux = MetaTypes '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish ]+-- | Debian and derivatives.+type DebianLike = MetaTypes '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish ]+type Debian = MetaTypes '[ 'Targeting 'OSDebian ]+type Buntish = MetaTypes '[ 'Targeting 'OSBuntish ]+type FreeBSD = MetaTypes '[ 'Targeting 'OSFreeBSD ]++-- | Used to indicate that a Property adds Info to the Host where it's used.+type HasInfo = MetaTypes '[ 'WithInfo ]++type family IncludesInfo t :: Bool+type instance IncludesInfo (MetaTypes l) = Elem 'WithInfo l++type MetaTypes = Sing++-- This boilerplate would not be needed if the singletons library were+-- used. However, we're targeting too old a version of ghc to use it yet.+data instance Sing (x :: MetaType) where+ OSDebianS :: Sing ('Targeting 'OSDebian)+ OSBuntishS :: Sing ('Targeting 'OSBuntish)+ OSFreeBSDS :: Sing ('Targeting 'OSFreeBSD)+ WithInfoS :: Sing 'WithInfo+instance SingI ('Targeting 'OSDebian) where sing = OSDebianS+instance SingI ('Targeting 'OSBuntish) where sing = OSBuntishS+instance SingI ('Targeting 'OSFreeBSD) where sing = OSFreeBSDS+instance SingI 'WithInfo where sing = WithInfoS+instance SingKind ('KProxy :: KProxy MetaType) where+ type DemoteRep ('KProxy :: KProxy MetaType) = MetaType+ fromSing OSDebianS = Targeting OSDebian+ fromSing OSBuntishS = Targeting OSBuntish+ fromSing OSFreeBSDS = Targeting OSFreeBSD+ fromSing WithInfoS = WithInfo++-- | Convenience type operator to combine two `MetaTypes` lists.+--+-- For example:+--+-- > HasInfo + Debian+--+-- Which is shorthand for this type:+--+-- > MetaTypes '[WithInfo, Targeting OSDebian]+type family a + b :: ab+type instance (MetaTypes a) + (MetaTypes b) = MetaTypes (Concat a b)++type family Concat (list1 :: [a]) (list2 :: [a]) :: [a]+type instance Concat '[] bs = bs+type instance Concat (a ': as) bs = a ': (Concat as bs)++-- | Combine two MetaTypes lists, yielding a list+-- that has targets present in both, and nontargets present in either.+type family Combine (list1 :: [a]) (list2 :: [a]) :: [a]+type instance Combine (list1 :: [a]) (list2 :: [a]) =+ (Concat+ (NonTargets list1 `Union` NonTargets list2)+ (Targets list1 `Intersect` Targets list2)+ )++-- | Checks if two MetaTypes lists can be safely combined.+--+-- This should be used anywhere Combine is used, as an additional+-- constraint. For example:+--+-- > foo :: (CheckCombinable x y ~ 'CanCombine) => x -> y -> Combine x y+type family CheckCombinable (list1 :: [a]) (list2 :: [a]) :: CheckCombine+-- As a special case, if either list is empty, let it be combined with the+-- other. This relies on MetaTypes list always containing at least+-- one target, so can only happen if there's already been a type error.+-- This special case lets the type checker show only the original type+-- error, and not an extra error due to a later CheckCombinable constraint.+type instance CheckCombinable '[] list2 = 'CanCombine+type instance CheckCombinable list1 '[] = 'CanCombine+type instance CheckCombinable (l1 ': list1) (l2 ': list2) =+ CheckCombinable' (Combine (l1 ': list1) (l2 ': list2))+type family CheckCombinable' (combinedlist :: [a]) :: CheckCombine+type instance CheckCombinable' '[] = 'CannotCombineTargets+type instance CheckCombinable' (a ': rest) + = If (IsTarget a)+ 'CanCombine+ (CheckCombinable' rest)++data CheckCombine = CannotCombineTargets | CanCombine++-- | Every item in the subset must be in the superset.+--+-- The name of this was chosen to make type errors more understandable.+type family NotSuperset (superset :: [a]) (subset :: [a]) :: CheckCombine+type instance NotSuperset superset '[] = 'CanCombine+type instance NotSuperset superset (s ': rest) =+ If (Elem s superset)+ (NotSuperset superset rest)+ 'CannotCombineTargets++type family IsTarget (a :: t) :: Bool+type instance IsTarget ('Targeting a) = 'True+type instance IsTarget 'WithInfo = 'False++type family Targets (l :: [a]) :: [a]+type instance Targets '[] = '[]+type instance Targets (x ': xs) =+ If (IsTarget x)+ (x ': Targets xs)+ (Targets xs)++type family NonTargets (l :: [a]) :: [a]+type instance NonTargets '[] = '[]+type instance NonTargets (x ': xs) =+ If (IsTarget x)+ (NonTargets xs)+ (x ': NonTargets xs)++-- | Type level elem+type family Elem (a :: t) (list :: [t]) :: Bool+type instance Elem a '[] = 'False+type instance Elem a (b ': bs) = EqT a b || Elem a bs++-- | Type level union.+type family Union (list1 :: [a]) (list2 :: [a]) :: [a]+type instance Union '[] list2 = list2+type instance Union (a ': rest) list2 =+ If (Elem a list2 || Elem a rest)+ (Union rest list2)+ (a ': Union rest list2)++-- | Type level intersection. Duplicate list items are eliminated.+type family Intersect (list1 :: [a]) (list2 :: [a]) :: [a]+type instance Intersect '[] list2 = '[]+type instance Intersect (a ': rest) list2 = + If (Elem a list2 && Not (Elem a rest))+ (a ': Intersect rest list2)+ (Intersect rest list2)++-- | Type level equality+--+-- This is a very clumsy implmentation, but it works back to ghc 7.6.+type family EqT (a :: t) (b :: t) :: Bool+type instance EqT ('Targeting a) ('Targeting b) = EqT a b+type instance EqT 'WithInfo 'WithInfo = 'True+type instance EqT 'WithInfo ('Targeting b) = 'False+type instance EqT ('Targeting a) 'WithInfo = 'False+type instance EqT 'OSDebian 'OSDebian = 'True+type instance EqT 'OSBuntish 'OSBuntish = 'True+type instance EqT 'OSFreeBSD 'OSFreeBSD = 'True+type instance EqT 'OSDebian 'OSBuntish = 'False+type instance EqT 'OSDebian 'OSFreeBSD = 'False+type instance EqT 'OSBuntish 'OSDebian = 'False+type instance EqT 'OSBuntish 'OSFreeBSD = 'False+type instance EqT 'OSFreeBSD 'OSDebian = 'False+type instance EqT 'OSFreeBSD 'OSBuntish = 'False+-- More modern version if the combinatiorial explosion gets too bad later:+--+-- type family Eq (a :: MetaType) (b :: MetaType) where+-- Eq a a = True+-- Eq a b = False++-- | An equivilant to the following is in Data.Type.Bool in+-- modern versions of ghc, but is included here to support ghc 7.6.+type family If (cond :: Bool) (tru :: a) (fls :: a) :: a+type instance If 'True tru fls = tru+type instance If 'False tru fls = fls+type family (a :: Bool) || (b :: Bool) :: Bool+type instance 'False || 'False = 'False+type instance 'True || 'True = 'True+type instance 'True || 'False = 'True+type instance 'False || 'True = 'True+type family (a :: Bool) && (b :: Bool) :: Bool+type instance 'False && 'False = 'False+type instance 'True && 'True = 'True+type instance 'True && 'False = 'False+type instance 'False && 'True = 'False+type family Not (a :: Bool) :: Bool+type instance Not 'False = 'True+type instance Not 'True = 'False+
src/Propellor/Types/OS.hs view
@@ -3,6 +3,7 @@ module Propellor.Types.OS ( System(..), Distribution(..),+ TargetOS(..), DebianSuite(..), FreeBSDRelease(..), FBSDVersion(..),@@ -16,6 +17,7 @@ userGroup, Port(..), fromPort,+ systemToTargetOS, ) where import Network.BSD (HostName)@@ -28,10 +30,23 @@ data Distribution = Debian DebianSuite- | Buntish Release -- ^ A well-known Debian derivative founded by a space tourist. The actual name of this distribution is not used in Propellor per <http://joeyh.name/blog/entry/trademark_nonsense/>)+ | Buntish Release -- ^ A well-known Debian derivative founded by a space tourist. The actual name of this distribution is not used in Propellor per <http://joeyh.name/blog/entry/trademark_nonsense/> | FreeBSD FreeBSDRelease deriving (Show, Eq) +-- | Properties can target one or more OS's; the targets are part+-- of the type of the property, so need to be kept fairly simple.+data TargetOS+ = OSDebian+ | OSBuntish+ | OSFreeBSD+ deriving (Show, Eq, Ord)++systemToTargetOS :: System -> TargetOS+systemToTargetOS (System (Debian _) _) = OSDebian+systemToTargetOS (System (Buntish _) _) = OSBuntish+systemToTargetOS (System (FreeBSD _) _) = OSFreeBSD+ -- | Debian has several rolling suites, and a number of stable releases, -- such as Stable "jessie". data DebianSuite = Experimental | Unstable | Testing | Stable Release@@ -39,10 +54,10 @@ -- | FreeBSD breaks their releases into "Production" and "Legacy". data FreeBSDRelease = FBSDProduction FBSDVersion | FBSDLegacy FBSDVersion- deriving (Show, Eq)+ deriving (Show, Eq) data FBSDVersion = FBSD101 | FBSD102 | FBSD093- deriving (Eq)+ deriving (Eq) instance IsString FBSDVersion where fromString "10.1-RELEASE" = FBSD101
src/Propellor/Types/ResultCheck.hs view
@@ -22,6 +22,9 @@ -- and `FailedChange` is still an error. data UncheckedProperty i = UncheckedProperty (Property i) +instance TightenTargets UncheckedProperty where+ tightenTargets (UncheckedProperty p) = UncheckedProperty (tightenTargets p)+ -- | Use to indicate that a Property is unchecked. unchecked :: Property i -> UncheckedProperty i unchecked = UncheckedProperty
+ src/Propellor/Types/Singletons.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE CPP, DataKinds, PolyKinds, TypeOperators, TypeFamilies, GADTs, UndecidableInstances #-}++-- | Simple implementation of singletons, portable back to ghc 7.6.3++module Propellor.Types.Singletons (+ module Propellor.Types.Singletons,+ KProxy(..)+) where++#if __GLASGOW_HASKELL__ > 707+import Data.Proxy (KProxy(..))+#else+data KProxy (a :: *) = KProxy+#endif++-- | The data family of singleton types.+data family Sing (x :: k)++-- | A class used to pass singleton values implicitly.+class SingI t where+ sing :: Sing t++-- Lists of singletons+data instance Sing (x :: [k]) where+ Nil :: Sing '[]+ Cons :: Sing x -> Sing xs -> Sing (x ': xs)+instance (SingI x, SingI xs) => SingI (x ': xs) where sing = Cons sing sing+instance SingI '[] where sing = Nil++data instance Sing (x :: Bool) where+ TrueS :: Sing 'True+ FalseS :: Sing 'False+instance SingI 'True where sing = TrueS+instance SingI 'False where sing = FalseS++class (kparam ~ 'KProxy) => SingKind (kparam :: KProxy k) where+ type DemoteRep kparam :: *+ -- | From singleton to value.+ fromSing :: Sing (a :: k) -> DemoteRep kparam++instance SingKind ('KProxy :: KProxy a) => SingKind ('KProxy :: KProxy [a]) where+ type DemoteRep ('KProxy :: KProxy [a]) = [DemoteRep ('KProxy :: KProxy a)]+ fromSing Nil = []+ fromSing (Cons x xs) = fromSing x : fromSing xs++instance SingKind ('KProxy :: KProxy Bool) where+ type DemoteRep ('KProxy :: KProxy Bool) = Bool+ fromSing FalseS = False+ fromSing TrueS = True
− src/System/Console/Concurrent.hs
@@ -1,44 +0,0 @@--- | --- Copyright: 2015 Joey Hess <id@joeyh.name>--- License: BSD-2-clause--- --- Concurrent output handling.------ > import Control.Concurrent.Async--- > import System.Console.Concurrent--- >--- > main = withConcurrentOutput $--- > outputConcurrent "washed the car\n"--- > `concurrently`--- > outputConcurrent "walked the dog\n"--- > `concurrently`--- > createProcessConcurrent (proc "ls" [])--{-# LANGUAGE CPP #-}--module System.Console.Concurrent (- -- * Concurrent output- withConcurrentOutput,- Outputable(..),- outputConcurrent,- errorConcurrent,- ConcurrentProcessHandle,-#ifndef mingw32_HOST_OS- createProcessConcurrent,-#endif- waitForProcessConcurrent,- createProcessForeground,- flushConcurrentOutput,- lockOutput,- -- * Low level access to the output buffer- OutputBuffer,- StdHandle(..),- bufferOutputSTM,- outputBufferWaiterSTM,- waitAnyBuffer,- waitCompleteLines,- emitOutputBuffer,-) where--import System.Console.Concurrent.Internal-
− src/System/Console/Concurrent/Internal.hs
@@ -1,556 +0,0 @@-{-# LANGUAGE BangPatterns, TypeSynonymInstances, FlexibleInstances, TupleSections #-}-{-# LANGUAGE CPP #-}---- | --- Copyright: 2015 Joey Hess <id@joeyh.name>--- License: BSD-2-clause--- --- Concurrent output handling, internals.------ May change at any time.--module System.Console.Concurrent.Internal where--import System.IO-#ifndef mingw32_HOST_OS-import System.Posix.IO-#endif-import System.Directory-import System.Exit-import Control.Monad-import Control.Monad.IO.Class (liftIO, MonadIO)-import System.IO.Unsafe (unsafePerformIO)-import Control.Concurrent-import Control.Concurrent.STM-import Control.Concurrent.Async-import Data.Maybe-import Data.List-import Data.Monoid-import qualified System.Process as P-import qualified Data.Text as T-import qualified Data.Text.IO as T-import Control.Applicative-import Prelude-import System.Log.Logger--import Utility.Monad-import Utility.Exception--data OutputHandle = OutputHandle- { outputLock :: TMVar Lock- , outputBuffer :: TMVar OutputBuffer- , errorBuffer :: TMVar OutputBuffer- , outputThreads :: TMVar Integer- , processWaiters :: TMVar [Async ()]- , waitForProcessLock :: TMVar ()- }--data Lock = Locked---- | A shared global variable for the OutputHandle.-{-# NOINLINE globalOutputHandle #-}-globalOutputHandle :: OutputHandle-globalOutputHandle = unsafePerformIO $ OutputHandle- <$> newEmptyTMVarIO- <*> newTMVarIO (OutputBuffer [])- <*> newTMVarIO (OutputBuffer [])- <*> newTMVarIO 0- <*> newTMVarIO []- <*> newEmptyTMVarIO---- | Holds a lock while performing an action. This allows the action to--- perform its own output to the console, without using functions from this--- module.------ While this is running, other threads that try to lockOutput will block.--- Any calls to `outputConcurrent` and `createProcessConcurrent` will not--- block, but the output will be buffered and displayed only once the--- action is done.-lockOutput :: (MonadIO m, MonadMask m) => m a -> m a-lockOutput = bracket_ (liftIO takeOutputLock) (liftIO dropOutputLock)---- | Blocks until we have the output lock.-takeOutputLock :: IO ()-takeOutputLock = void $ takeOutputLock' True---- | Tries to take the output lock, without blocking.-tryTakeOutputLock :: IO Bool-tryTakeOutputLock = takeOutputLock' False--withLock :: (TMVar Lock -> STM a) -> IO a-withLock a = atomically $ a (outputLock globalOutputHandle)--takeOutputLock' :: Bool -> IO Bool-takeOutputLock' block = do- locked <- withLock $ \l -> do- v <- tryTakeTMVar l- case v of- Just Locked- | block -> retry- | otherwise -> do- -- Restore value we took.- putTMVar l Locked- return False- Nothing -> do- putTMVar l Locked- return True- when locked $ do- (outbuf, errbuf) <- atomically $ (,)- <$> swapTMVar (outputBuffer globalOutputHandle) (OutputBuffer [])- <*> swapTMVar (errorBuffer globalOutputHandle) (OutputBuffer [])- emitOutputBuffer StdOut outbuf- emitOutputBuffer StdErr errbuf- return locked---- | Only safe to call after taking the output lock.-dropOutputLock :: IO ()-dropOutputLock = withLock $ void . takeTMVar---- | Use this around any actions that use `outputConcurrent`--- or `createProcessConcurrent`------ This is necessary to ensure that buffered concurrent output actually--- gets displayed before the program exits.-withConcurrentOutput :: (MonadIO m, MonadMask m) => m a -> m a-withConcurrentOutput a = a `finally` liftIO flushConcurrentOutput---- | Blocks until any processes started by `createProcessConcurrent` have--- finished, and any buffered output is displayed. Also blocks while--- `lockOutput` is is use.------ `withConcurrentOutput` calls this at the end, so you do not normally--- need to use this.-flushConcurrentOutput :: IO ()-flushConcurrentOutput = do- atomically $ do- r <- takeTMVar (outputThreads globalOutputHandle)- if r <= 0- then putTMVar (outputThreads globalOutputHandle) r- else retry- -- Take output lock to wait for anything else that might be- -- currently generating output.- lockOutput $ return ()---- | Values that can be output.-class Outputable v where- toOutput :: v -> T.Text--instance Outputable T.Text where- toOutput = id--instance Outputable String where- toOutput = toOutput . T.pack---- | Displays a value to stdout.------ No newline is appended to the value, so if you want a newline, be sure--- to include it yourself.------ Uses locking to ensure that the whole output occurs atomically--- even when other threads are concurrently generating output.------ When something else is writing to the console at the same time, this does--- not block. It buffers the value, so it will be displayed once the other--- writer is done.-outputConcurrent :: Outputable v => v -> IO ()-outputConcurrent = outputConcurrent' StdOut---- | Like `outputConcurrent`, but displays to stderr.------ (Does not throw an exception.)-errorConcurrent :: Outputable v => v -> IO ()-errorConcurrent = outputConcurrent' StdErr--outputConcurrent' :: Outputable v => StdHandle -> v -> IO ()-outputConcurrent' stdh v = bracket setup cleanup go- where- setup = tryTakeOutputLock- cleanup False = return ()- cleanup True = dropOutputLock- go True = do- T.hPutStr h (toOutput v)- hFlush h- go False = do- oldbuf <- atomically $ takeTMVar bv- newbuf <- addOutputBuffer (Output (toOutput v)) oldbuf- atomically $ putTMVar bv newbuf- h = toHandle stdh- bv = bufferFor stdh--newtype ConcurrentProcessHandle = ConcurrentProcessHandle P.ProcessHandle--toConcurrentProcessHandle :: (Maybe Handle, Maybe Handle, Maybe Handle, P.ProcessHandle) -> (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle)-toConcurrentProcessHandle (i, o, e, h) = (i, o, e, ConcurrentProcessHandle h)---- | Use this to wait for processes started with --- `createProcessConcurrent` and `createProcessForeground`, and get their--- exit status.------ Note that such processes are actually automatically waited for--- internally, so not calling this explicitly will not result--- in zombie processes. This behavior differs from `P.waitForProcess`-waitForProcessConcurrent :: ConcurrentProcessHandle -> IO ExitCode-waitForProcessConcurrent (ConcurrentProcessHandle h) = - bracket lock unlock checkexit- where- lck = waitForProcessLock globalOutputHandle- lock = atomically $ tryPutTMVar lck ()- unlock True = atomically $ takeTMVar lck- unlock False = return ()- checkexit locked = maybe (waitsome locked) return- =<< P.getProcessExitCode h- waitsome True = do- let v = processWaiters globalOutputHandle- l <- atomically $ readTMVar v- if null l- -- Avoid waitAny [] which blocks forever- then P.waitForProcess h- else do- -- Wait for any of the running- -- processes to exit. It may or may not- -- be the one corresponding to the- -- ProcessHandle. If it is,- -- getProcessExitCode will succeed.- void $ tryIO $ waitAny l- checkexit True- waitsome False = do- -- Another thread took the lck first. Wait for that thread to- -- wait for one of the running processes to exit.- atomically $ do- putTMVar lck ()- takeTMVar lck- checkexit False---- Registers an action that waits for a process to exit,--- adding it to the processWaiters list, and removing it once the action--- completes.-asyncProcessWaiter :: IO () -> IO ()-asyncProcessWaiter waitaction = do- regdone <- newEmptyTMVarIO- waiter <- async $ do- self <- atomically (takeTMVar regdone)- waitaction `finally` unregister self- register waiter regdone- where- v = processWaiters globalOutputHandle- register waiter regdone = atomically $ do- l <- takeTMVar v- putTMVar v (waiter:l)- putTMVar regdone waiter- unregister waiter = atomically $ do- l <- takeTMVar v- putTMVar v (filter (/= waiter) l)---- | Wrapper around `System.Process.createProcess` that prevents --- multiple processes that are running concurrently from writing--- to stdout/stderr at the same time.------ If the process does not output to stdout or stderr, it's run--- by createProcess entirely as usual. Only processes that can generate--- output are handled specially:------ A process is allowed to write to stdout and stderr in the usual--- way, assuming it can successfully take the output lock.------ When the output lock is held (ie, by another concurrent process,--- or because `outputConcurrent` is being called at the same time),--- the process is instead run with its stdout and stderr--- redirected to a buffer. The buffered output will be displayed as soon--- as the output lock becomes free.------ Currently only available on Unix systems, not Windows.-#ifndef mingw32_HOST_OS-createProcessConcurrent :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle) -createProcessConcurrent p- | willOutput (P.std_out p) || willOutput (P.std_err p) =- ifM tryTakeOutputLock- ( fgProcess p- , bgProcess p- )- | otherwise = do- r@(_, _, _, h) <- P.createProcess p- asyncProcessWaiter $- void $ tryIO $ P.waitForProcess h- return (toConcurrentProcessHandle r)-#endif---- | Wrapper around `System.Process.createProcess` that makes sure a process--- is run in the foreground, with direct access to stdout and stderr.--- Useful when eg, running an interactive process.-createProcessForeground :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle)-createProcessForeground p = do- takeOutputLock- fgProcess p--fgProcess :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle)-fgProcess p = do- r@(_, _, _, h) <- P.createProcess p- `onException` dropOutputLock- registerOutputThread- debug ["fgProcess", showProc p]- -- Wait for the process to exit and drop the lock.- asyncProcessWaiter $ do- void $ tryIO $ P.waitForProcess h- unregisterOutputThread- dropOutputLock- debug ["fgProcess done", showProc p]- return (toConcurrentProcessHandle r)- -debug :: [String] -> IO ()-debug = debugM "concurrent-output" . unwords--showProc :: P.CreateProcess -> String-showProc = go . P.cmdspec- where- go (P.ShellCommand s) = s- go (P.RawCommand c ps) = show (c, ps)--#ifndef mingw32_HOST_OS-bgProcess :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle)-bgProcess p = do- (toouth, fromouth) <- pipe- (toerrh, fromerrh) <- pipe- debug ["bgProcess", showProc p]- let p' = p- { P.std_out = rediroutput (P.std_out p) toouth- , P.std_err = rediroutput (P.std_err p) toerrh- }- registerOutputThread- r@(_, _, _, h) <- P.createProcess p'- `onException` unregisterOutputThread- asyncProcessWaiter $ void $ tryIO $ P.waitForProcess h- outbuf <- setupOutputBuffer StdOut toouth (P.std_out p) fromouth- errbuf <- setupOutputBuffer StdErr toerrh (P.std_err p) fromerrh- void $ async $ bufferWriter [outbuf, errbuf]- return (toConcurrentProcessHandle r)- where- pipe = do- (from, to) <- createPipe- (,) <$> fdToHandle to <*> fdToHandle from- rediroutput ss h- | willOutput ss = P.UseHandle h- | otherwise = ss-#endif--willOutput :: P.StdStream -> Bool-willOutput P.Inherit = True-willOutput _ = False---- | Buffered output.-data OutputBuffer = OutputBuffer [OutputBufferedActivity]- deriving (Eq)--data StdHandle = StdOut | StdErr--toHandle :: StdHandle -> Handle-toHandle StdOut = stdout-toHandle StdErr = stderr--bufferFor :: StdHandle -> TMVar OutputBuffer-bufferFor StdOut = outputBuffer globalOutputHandle-bufferFor StdErr = errorBuffer globalOutputHandle--data OutputBufferedActivity- = Output T.Text- | InTempFile- { tempFile :: FilePath- , endsInNewLine :: Bool- }- deriving (Eq)--data AtEnd = AtEnd- deriving Eq--data BufSig = BufSig--setupOutputBuffer :: StdHandle -> Handle -> P.StdStream -> Handle -> IO (StdHandle, MVar OutputBuffer, TMVar BufSig, TMVar AtEnd)-setupOutputBuffer h toh ss fromh = do- hClose toh- buf <- newMVar (OutputBuffer [])- bufsig <- atomically newEmptyTMVar- bufend <- atomically newEmptyTMVar- void $ async $ outputDrainer ss fromh buf bufsig bufend- return (h, buf, bufsig, bufend)---- Drain output from the handle, and buffer it.-outputDrainer :: P.StdStream -> Handle -> MVar OutputBuffer -> TMVar BufSig -> TMVar AtEnd -> IO ()-outputDrainer ss fromh buf bufsig bufend- | willOutput ss = go- | otherwise = atend- where- go = do- t <- T.hGetChunk fromh- if T.null t- then atend- else do- modifyMVar_ buf $ addOutputBuffer (Output t)- changed- go- atend = do- atomically $ putTMVar bufend AtEnd- hClose fromh- changed = atomically $ do- void $ tryTakeTMVar bufsig- putTMVar bufsig BufSig--registerOutputThread :: IO ()-registerOutputThread = do- let v = outputThreads globalOutputHandle- atomically $ putTMVar v . succ =<< takeTMVar v- -unregisterOutputThread :: IO ()-unregisterOutputThread = do- let v = outputThreads globalOutputHandle- atomically $ putTMVar v . pred =<< takeTMVar v---- Wait to lock output, and once we can, display everything --- that's put into the buffers, until the end.------ If end is reached before lock is taken, instead add the command's--- buffers to the global outputBuffer and errorBuffer.-bufferWriter :: [(StdHandle, MVar OutputBuffer, TMVar BufSig, TMVar AtEnd)] -> IO ()-bufferWriter ts = do- activitysig <- atomically newEmptyTMVar- worker1 <- async $ lockOutput $- ifM (atomically $ tryPutTMVar activitysig ())- ( void $ mapConcurrently displaybuf ts- , noop -- buffers already moved to global- )- worker2 <- async $ void $ globalbuf activitysig worker1- void $ async $ do- void $ waitCatch worker1- void $ waitCatch worker2- unregisterOutputThread- where- displaybuf v@(outh, buf, bufsig, bufend) = do- change <- atomically $- (Right <$> takeTMVar bufsig)- `orElse`- (Left <$> takeTMVar bufend)- l <- takeMVar buf- putMVar buf (OutputBuffer [])- emitOutputBuffer outh l- case change of- Right BufSig -> displaybuf v- Left AtEnd -> return ()- globalbuf activitysig worker1 = do- ok <- atomically $ do- -- signal we're going to handle it- -- (returns false if the displaybuf already did)- ok <- tryPutTMVar activitysig ()- -- wait for end of all buffers- when ok $- mapM_ (\(_outh, _buf, _bufsig, bufend) -> takeTMVar bufend) ts- return ok- when ok $ do- -- add all of the command's buffered output to the- -- global output buffer, atomically- bs <- forM ts $ \(outh, buf, _bufsig, _bufend) ->- (outh,) <$> takeMVar buf- atomically $- forM_ bs $ \(outh, b) -> - bufferOutputSTM' outh b- -- worker1 might be blocked waiting for the output- -- lock, and we've already done its job, so cancel it- cancel worker1---- Adds a value to the OutputBuffer. When adding Output to a Handle,--- it's cheaper to combine it with any already buffered Output to that--- same Handle.------ When the total buffered Output exceeds 1 mb in size, it's moved out of--- memory, to a temp file. This should only happen rarely, but is done to--- avoid some verbose process unexpectedly causing excessive memory use.-addOutputBuffer :: OutputBufferedActivity -> OutputBuffer -> IO OutputBuffer-addOutputBuffer (Output t) (OutputBuffer buf)- | T.length t' <= 1048576 = return $ OutputBuffer (Output t' : other)- | otherwise = do- tmpdir <- getTemporaryDirectory- (tmp, h) <- openTempFile tmpdir "output.tmp"- let !endnl = endsNewLine t'- let i = InTempFile- { tempFile = tmp- , endsInNewLine = endnl- }- T.hPutStr h t'- hClose h- return $ OutputBuffer (i : other)- where- !t' = T.concat (mapMaybe getOutput this) <> t- !(this, other) = partition isOutput buf- isOutput v = case v of- Output _ -> True- _ -> False- getOutput v = case v of- Output t'' -> Just t''- _ -> Nothing-addOutputBuffer v (OutputBuffer buf) = return $ OutputBuffer (v:buf)---- | Adds a value to the output buffer for later display.------ Note that buffering large quantities of data this way will keep it--- resident in memory until it can be displayed. While `outputConcurrent`--- uses temp files if the buffer gets too big, this STM function cannot do--- so.-bufferOutputSTM :: Outputable v => StdHandle -> v -> STM ()-bufferOutputSTM h v = bufferOutputSTM' h (OutputBuffer [Output (toOutput v)])--bufferOutputSTM' :: StdHandle -> OutputBuffer -> STM ()-bufferOutputSTM' h (OutputBuffer newbuf) = do- (OutputBuffer buf) <- takeTMVar bv- putTMVar bv (OutputBuffer (newbuf ++ buf))- where- bv = bufferFor h---- | A STM action that waits for some buffered output to become--- available, and returns it.------ The function can select a subset of output when only some is desired;--- the fst part is returned and the snd is left in the buffer.------ This will prevent it from being displayed in the usual way, so you'll--- need to use `emitOutputBuffer` to display it yourself.-outputBufferWaiterSTM :: (OutputBuffer -> (OutputBuffer, OutputBuffer)) -> STM (StdHandle, OutputBuffer)-outputBufferWaiterSTM selector = waitgetbuf StdOut `orElse` waitgetbuf StdErr- where- waitgetbuf h = do- let bv = bufferFor h- (selected, rest) <- selector <$> takeTMVar bv- when (selected == OutputBuffer [])- retry- putTMVar bv rest- return (h, selected)--waitAnyBuffer :: OutputBuffer -> (OutputBuffer, OutputBuffer)-waitAnyBuffer b = (b, OutputBuffer [])---- | Use with `outputBufferWaiterSTM` to make it only return buffered--- output that ends with a newline. Anything buffered without a newline--- is left in the buffer.-waitCompleteLines :: OutputBuffer -> (OutputBuffer, OutputBuffer)-waitCompleteLines (OutputBuffer l) = - let (selected, rest) = span completeline l- in (OutputBuffer selected, OutputBuffer rest)- where- completeline (v@(InTempFile {})) = endsInNewLine v- completeline (Output b) = endsNewLine b--endsNewLine :: T.Text -> Bool-endsNewLine t = not (T.null t) && T.last t == '\n'---- | Emits the content of the OutputBuffer to the Handle------ If you use this, you should use `lockOutput` to ensure you're the only--- thread writing to the console.-emitOutputBuffer :: StdHandle -> OutputBuffer -> IO ()-emitOutputBuffer stdh (OutputBuffer l) = - forM_ (reverse l) $ \ba -> case ba of- Output t -> emit t- InTempFile tmp _ -> do- emit =<< T.readFile tmp- void $ tryWhenExists $ removeFile tmp- where- outh = toHandle stdh- emit t = void $ tryIO $ do- T.hPutStr outh t- hFlush outh
− src/System/Process/Concurrent.hs
@@ -1,34 +0,0 @@--- | --- Copyright: 2015 Joey Hess <id@joeyh.name>--- License: BSD-2-clause--- --- The functions exported by this module are intended to be drop-in--- replacements for those from System.Process, when converting a whole--- program to use System.Console.Concurrent.--module System.Process.Concurrent where--import System.Console.Concurrent-import System.Console.Concurrent.Internal (ConcurrentProcessHandle(..))-import System.Process hiding (createProcess, waitForProcess)-import System.IO-import System.Exit---- | Calls `createProcessConcurrent`------ You should use the waitForProcess in this module on the resulting--- ProcessHandle. Using System.Process.waitForProcess instead can have--- mildly unexpected results.-createProcess :: CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)-createProcess p = do- (i, o, e, ConcurrentProcessHandle h) <- createProcessConcurrent p- return (i, o, e, h)---- | Calls `waitForProcessConcurrent`------ You should only use this on a ProcessHandle obtained by calling--- createProcess from this module. Using this with a ProcessHandle--- obtained from System.Process.createProcess etc will have extremely--- unexpected results; it can wait a very long time before returning.-waitForProcess :: ProcessHandle -> IO ExitCode-waitForProcess = waitForProcessConcurrent . ConcurrentProcessHandle
src/config.hs view
@@ -4,15 +4,8 @@ import Propellor import qualified Propellor.Property.File as File import qualified Propellor.Property.Apt as Apt-import qualified Propellor.Property.Network as Network---import qualified Propellor.Property.Ssh as Ssh import qualified Propellor.Property.Cron as Cron-import Propellor.Property.Scheduled---import qualified Propellor.Property.Sudo as Sudo import qualified Propellor.Property.User as User---import qualified Propellor.Property.Hostname as Hostname---import qualified Propellor.Property.Tor as Tor-import qualified Propellor.Property.Docker as Docker main :: IO () main = defaultMain hosts@@ -25,24 +18,12 @@ -- An example host. mybox :: Host-mybox = host "mybox.example.com"- & os (System (Debian Unstable) "amd64")+mybox = host "mybox.example.com" $ props+ & osDebian Unstable "amd64" & Apt.stdSourcesList & Apt.unattendedUpgrades & Apt.installed ["etckeeper"] & Apt.installed ["ssh"] & User.hasSomePassword (User "root")- & Network.ipv6to4 & File.dirExists "/var/www"- & Docker.docked webserverContainer- & Docker.garbageCollected `period` Daily & Cron.runPropellor (Cron.Times "30 * * * *")---- A generic webserver in a Docker container.-webserverContainer :: Docker.Container-webserverContainer = Docker.container "webserver" (Docker.latestImage "debian")- & os (System (Debian (Stable "jessie")) "amd64")- & Apt.stdSourcesList- & Docker.publish "80:80"- & Docker.volume "/var/www:/var/www"- & Apt.serviceInstalledRunning "apache2"
src/wrapper.hs view
@@ -3,169 +3,42 @@ -- Distributions should install this program into PATH. -- (Cabal builds it as dist/build/propellor/propellor). ----- This is not the propellor main program (that's config.hs)------ This installs propellor's source into ~/.propellor,--- uses it to build the real propellor program (if not already built),--- and runs it.--- --- The source is cloned from /usr/src/propellor when available,--- or is cloned from git over the network.+-- This is not the propellor main program (that's config.hs).+-- This bootstraps ~/.propellor/config.hs, builds it if+-- it's not already built, and runs it. module Main where +import Propellor.DotDir import Propellor.Message import Propellor.Bootstrap-import Utility.UserInfo import Utility.Monad import Utility.Process-import Utility.SafeCommand-import Utility.Exception+import Utility.Process.NonConcurrent -import Control.Monad-import Control.Monad.IfElse import System.Directory-import System.FilePath import System.Environment (getArgs) import System.Exit import System.Posix.Directory-import System.IO-import Control.Applicative-import Prelude--distdir :: FilePath-distdir = "/usr/src/propellor"--distrepo :: FilePath-distrepo = distdir </> "propellor.git"--disthead :: FilePath-disthead = distdir </> "head"--upstreambranch :: String-upstreambranch = "upstream/master"---- Using the github mirror of the main propellor repo because--- it is accessible over https for better security.-netrepo :: String-netrepo = "https://github.com/joeyh/propellor.git"+import Control.Monad.IfElse main :: IO ()-main = withConcurrentOutput $ do- args <- getArgs- home <- myHomeDir- let propellordir = home </> ".propellor"- let propellorbin = propellordir </> "propellor"- wrapper args propellordir propellorbin--wrapper :: [String] -> FilePath -> FilePath -> IO ()-wrapper args propellordir propellorbin = do- ifM (doesDirectoryExist propellordir)- ( checkRepo - , makeRepo- )- buildruncfg+main = withConcurrentOutput $ go =<< getArgs where- makeRepo = do- putStrLn $ "Setting up your propellor repo in " ++ propellordir- putStrLn ""- ifM (doesFileExist distrepo <||> doesDirectoryExist distrepo)- ( do - void $ boolSystem "git" [Param "clone", File distrepo, File propellordir]- fetchUpstreamBranch propellordir distrepo- changeWorkingDirectory propellordir- void $ boolSystem "git" [Param "remote", Param "rm", Param "origin"]- , do- void $ boolSystem "git" [Param "clone", Param netrepo, File propellordir]- changeWorkingDirectory propellordir- -- Rename origin to upstream and avoid- -- git push to that read-only repo.- void $ boolSystem "git" [Param "remote", Param "rename", Param "origin", Param "upstream"]- void $ boolSystem "git" [Param "config", Param "--unset", Param "branch.master.remote", Param "upstream"]- )+ go ["--init"] = interactiveInit+ go args = ifM (doesDirectoryExist =<< dotPropellor)+ ( do+ checkRepoUpToDate+ buildRunConfig args+ , error "Seems that ~/.propellor/ does not exist. To set it up, run: propellor --init"+ ) - checkRepo = whenM (doesFileExist disthead <&&> doesFileExist (propellordir </> "propellor.cabal")) $ do- headrev <- takeWhile (/= '\n') <$> readFile disthead- changeWorkingDirectory propellordir- headknown <- catchMaybeIO $ - withQuietOutput createProcessSuccess $- proc "git" ["log", headrev]- if (headknown == Nothing)- then setupupstreammaster headrev propellordir- else do- merged <- not . null <$>- readProcess "git" ["log", headrev ++ "..HEAD", "--ancestry-path"]- unless merged $- warnoutofdate propellordir True- buildruncfg = do- changeWorkingDirectory propellordir+buildRunConfig :: [String] -> IO ()+buildRunConfig args = do+ changeWorkingDirectory =<< dotPropellor+ unlessM (doesFileExist "propellor") $ do buildPropellor Nothing putStrLn "" putStrLn ""- chain- chain = do- (_, _, _, pid) <- createProcess (proc propellorbin args) - exitWith =<< waitForProcess pid---- Passed the user's propellordir repository, makes upstream/master--- be a usefully mergeable branch.------ We cannot just use origin/master, because in the case of a distrepo,--- it only contains 1 commit. So, trying to merge with it will result--- in lots of merge conflicts, since git cannot find a common parent--- commit.------ Instead, the upstream/master branch is created by taking the--- upstream/master branch (which must be an old version of propellor,--- as distributed), and diffing from it to the current origin/master,--- and committing the result. This is done in a temporary clone of the--- repository, giving it a new master branch. That new branch is fetched--- into the user's repository, as if fetching from a upstream remote,--- yielding a new upstream/master branch.-setupupstreammaster :: String -> FilePath -> IO ()-setupupstreammaster newref propellordir = do- changeWorkingDirectory propellordir- go =<< catchMaybeIO getoldrev- where- go Nothing = warnoutofdate propellordir False- go (Just oldref) = do- let tmprepo = ".git/propellordisttmp"- let cleantmprepo = void $ catchMaybeIO $ removeDirectoryRecursive tmprepo- cleantmprepo- git ["clone", "--quiet", ".", tmprepo]- - changeWorkingDirectory tmprepo- git ["fetch", distrepo, "--quiet"]- git ["reset", "--hard", oldref, "--quiet"]- git ["merge", newref, "-s", "recursive", "-Xtheirs", "--quiet", "-m", "merging upstream version"]- - fetchUpstreamBranch propellordir tmprepo- cleantmprepo- warnoutofdate propellordir True-- getoldrev = takeWhile (/= '\n')- <$> readProcess "git" ["show-ref", upstreambranch, "--hash"]- - git = run "git"- run cmd ps = unlessM (boolSystem cmd (map Param ps)) $- error $ "Failed to run " ++ cmd ++ " " ++ show ps--warnoutofdate :: FilePath -> Bool -> IO ()-warnoutofdate propellordir havebranch = do- warningMessage ("** Your " ++ propellordir ++ " is out of date..")- let also s = hPutStrLn stderr (" " ++ s)- also ("A newer upstream version is available in " ++ distrepo)- if havebranch- then also ("To merge it, run: git merge " ++ upstreambranch)- else also ("To merge it, find the most recent commit in your repository's history that corresponds to an upstream release of propellor, and set refs/remotes/" ++ upstreambranch ++ " to it. Then run propellor again.")- also ""--fetchUpstreamBranch :: FilePath -> FilePath -> IO ()-fetchUpstreamBranch propellordir repo = do- changeWorkingDirectory propellordir- void $ boolSystem "git"- [ Param "fetch"- , File repo- , Param ("+refs/heads/master:refs/remotes/" ++ upstreambranch)- , Param "--quiet"- ]+ (_, _, _, pid) <- createProcessNonConcurrent (proc "./propellor" args) + exitWith =<< waitForProcessNonConcurrent pid
+ stack.yaml view
@@ -0,0 +1,3 @@+resolver: lts-5.10+packages:+- '.'