Merge branch 'joeyconfig'
Conflicts: doc/todo/info_propigation_out_of_nested_properties.mdwn privdata.joey/privdata.gpg
This commit is contained in:
commit
401b857eef
|
@ -25,6 +25,7 @@ import qualified Propellor.Property.Grub as Grub
|
||||||
import qualified Propellor.Property.Obnam as Obnam
|
import qualified Propellor.Property.Obnam as Obnam
|
||||||
import qualified Propellor.Property.Gpg as Gpg
|
import qualified Propellor.Property.Gpg as Gpg
|
||||||
import qualified Propellor.Property.Systemd as Systemd
|
import qualified Propellor.Property.Systemd as Systemd
|
||||||
|
import qualified Propellor.Property.Journald as Journald
|
||||||
import qualified Propellor.Property.OS as OS
|
import qualified Propellor.Property.OS as OS
|
||||||
import qualified Propellor.Property.HostingProvider.DigitalOcean as DigitalOcean
|
import qualified Propellor.Property.HostingProvider.DigitalOcean as DigitalOcean
|
||||||
import qualified Propellor.Property.HostingProvider.CloudAtCost as CloudAtCost
|
import qualified Propellor.Property.HostingProvider.CloudAtCost as CloudAtCost
|
||||||
|
@ -46,7 +47,6 @@ hosts = -- (o) `
|
||||||
, kite
|
, kite
|
||||||
, diatom
|
, diatom
|
||||||
, elephant
|
, elephant
|
||||||
, testvm
|
|
||||||
] ++ monsters
|
] ++ monsters
|
||||||
|
|
||||||
testvm :: Host
|
testvm :: Host
|
||||||
|
@ -140,11 +140,13 @@ kite = standardSystemUnhardened "kite.kitenet.net" Testing "amd64"
|
||||||
, (SshEd25519, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFZftKMnH/zH29BHMKbcBO4QsgTrstYFVhbrzrlRzBO3")
|
, (SshEd25519, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFZftKMnH/zH29BHMKbcBO4QsgTrstYFVhbrzrlRzBO3")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
& Network.static "eth0" `requires` Network.cleanInterfacesFile
|
||||||
& Apt.installed ["linux-image-amd64"]
|
& Apt.installed ["linux-image-amd64"]
|
||||||
& Linode.chainPVGrub 5
|
& Linode.chainPVGrub 5
|
||||||
& Apt.unattendedUpgrades
|
& Apt.unattendedUpgrades
|
||||||
& Systemd.installed
|
& Systemd.installed
|
||||||
& Systemd.persistentJournal
|
& Systemd.persistentJournal
|
||||||
|
& Journald.systemMaxUse "500MiB"
|
||||||
& Ssh.passwordAuthentication True
|
& Ssh.passwordAuthentication True
|
||||||
-- Since ssh password authentication is allowed:
|
-- Since ssh password authentication is allowed:
|
||||||
& Apt.serviceInstalledRunning "fail2ban"
|
& Apt.serviceInstalledRunning "fail2ban"
|
||||||
|
@ -254,7 +256,7 @@ diatom = standardSystem "diatom.kitenet.net" (Stable "wheezy") "amd64"
|
||||||
& JoeySites.oldUseNetServer hosts
|
& JoeySites.oldUseNetServer hosts
|
||||||
|
|
||||||
& alias "ns2.kitenet.net"
|
& alias "ns2.kitenet.net"
|
||||||
& myDnsPrimary False "kitenet.net" []
|
& myDnsPrimary True "kitenet.net" []
|
||||||
& myDnsPrimary True "joeyh.name" []
|
& myDnsPrimary True "joeyh.name" []
|
||||||
& myDnsPrimary True "ikiwiki.info" []
|
& myDnsPrimary True "ikiwiki.info" []
|
||||||
& myDnsPrimary True "olduse.net"
|
& myDnsPrimary True "olduse.net"
|
||||||
|
@ -327,13 +329,14 @@ elephant = standardSystem "elephant.kitenet.net" Unstable "amd64"
|
||||||
& Ssh.listenPort 80
|
& Ssh.listenPort 80
|
||||||
|
|
||||||
|
|
||||||
--' __|II| ,.
|
--' __|II| ,.
|
||||||
---- __|II|II|__ ( \_,/\
|
---- __|II|II|__ ( \_,/\
|
||||||
------'\o/-'-.-'-.-'-.- __|II|II|II|II|___/ __/ -'-.-'-.-'-.-'-.-'-
|
--'-------'\o/-'-.-'-.-'-.- __|II|II|II|II|___/ __/ -'-.-'-.-'-.-'-.-'-.-'-
|
||||||
----------------------- | [Docker] / ----------------------
|
-------------------------- | [Docker] / --------------------------
|
||||||
----------------------- : / -----------------------
|
-------------------------- : / ---------------------------
|
||||||
------------------------ \____, o ,' ------------------------
|
--------------------------- \____, o ,' ----------------------------
|
||||||
------------------------- '--,___________,' -------------------------
|
---------------------------- '--,___________,' -----------------------------
|
||||||
|
|
||||||
-- Simple web server, publishing the outside host's /var/www
|
-- Simple web server, publishing the outside host's /var/www
|
||||||
webserver :: Docker.Container
|
webserver :: Docker.Container
|
||||||
webserver = standardStableContainer "webserver"
|
webserver = standardStableContainer "webserver"
|
||||||
|
@ -434,13 +437,12 @@ dockerImage (System (Debian Testing) arch) = "joeyh/debian-unstable-" ++ arch
|
||||||
dockerImage (System (Debian (Stable _)) arch) = "joeyh/debian-stable-" ++ arch
|
dockerImage (System (Debian (Stable _)) arch) = "joeyh/debian-stable-" ++ arch
|
||||||
dockerImage _ = "debian-stable-official" -- does not currently exist!
|
dockerImage _ = "debian-stable-official" -- does not currently exist!
|
||||||
|
|
||||||
myDnsSecondary :: Property
|
myDnsSecondary :: Property HasInfo
|
||||||
myDnsSecondary = propertyList "dns secondary for all my domains" $ map toProp
|
myDnsSecondary = propertyList "dns secondary for all my domains" $ props
|
||||||
[ Dns.secondary hosts "kitenet.net"
|
& Dns.secondary hosts "kitenet.net"
|
||||||
, Dns.secondary hosts "joeyh.name"
|
& Dns.secondary hosts "joeyh.name"
|
||||||
, Dns.secondary hosts "ikiwiki.info"
|
& Dns.secondary hosts "ikiwiki.info"
|
||||||
, Dns.secondary hosts "olduse.net"
|
& Dns.secondary hosts "olduse.net"
|
||||||
]
|
|
||||||
|
|
||||||
branchableSecondary :: RevertableProperty
|
branchableSecondary :: RevertableProperty
|
||||||
branchableSecondary = Dns.secondaryFor ["branchable.com"] hosts "branchable.com"
|
branchableSecondary = Dns.secondaryFor ["branchable.com"] hosts "branchable.com"
|
||||||
|
@ -456,23 +458,11 @@ myDnsPrimary dnssec domain extras = (if dnssec then Dns.signedPrimary (Weekly No
|
||||||
, (RootDomain, NS $ AbsDomain "ns4.kitenet.net")
|
, (RootDomain, NS $ AbsDomain "ns4.kitenet.net")
|
||||||
, (RootDomain, NS $ AbsDomain "ns6.gandi.net")
|
, (RootDomain, NS $ AbsDomain "ns6.gandi.net")
|
||||||
, (RootDomain, MX 0 $ AbsDomain "kitenet.net")
|
, (RootDomain, MX 0 $ AbsDomain "kitenet.net")
|
||||||
-- SPF only allows IP address of kitenet.net to send mail.
|
, (RootDomain, TXT "v=spf1 a a:kitenet.net ~all")
|
||||||
, (RootDomain, TXT "v=spf1 a:kitenet.net -all")
|
|
||||||
, JoeySites.domainKey
|
, JoeySites.domainKey
|
||||||
] ++ extras
|
] ++ extras
|
||||||
|
|
||||||
|
|
||||||
-- o
|
|
||||||
-- ___ o o
|
|
||||||
{-----\ / o \ ___o o
|
|
||||||
{ \ __ \ / _ (X___>-- __o
|
|
||||||
_____________________{ ______\___ \__/ | \__/ \____ |X__>
|
|
||||||
< \___//|\\___/\ \____________ _
|
|
||||||
\ ___/ | \___ # # \ (-)
|
|
||||||
\ O O O # | \ # >=)
|
|
||||||
\______________________________# # / #__________________/ (-}
|
|
||||||
|
|
||||||
|
|
||||||
monsters :: [Host] -- Systems I don't manage with propellor,
|
monsters :: [Host] -- Systems I don't manage with propellor,
|
||||||
monsters = -- but do want to track their public keys etc.
|
monsters = -- but do want to track their public keys etc.
|
||||||
[ host "usw-s002.rsync.net"
|
[ host "usw-s002.rsync.net"
|
||||||
|
@ -508,3 +498,17 @@ monsters = -- but do want to track their public keys etc.
|
||||||
& ipv4 "76.7.162.101"
|
& ipv4 "76.7.162.101"
|
||||||
& ipv4 "76.7.162.186"
|
& ipv4 "76.7.162.186"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- o
|
||||||
|
-- ___ o o
|
||||||
|
{-----\ / o \ ___o o
|
||||||
|
{ \ __ \ / _ (X___>-- __o
|
||||||
|
_____________________{ ______\___ \__/ | \__/ \____ |X__>
|
||||||
|
< \___//|\\___/\ \____________ _
|
||||||
|
\ ___/ | \___ # # \ (-)
|
||||||
|
\ O O O # | \ # >=)
|
||||||
|
\______________________________# # / #__________________/ (-}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,31 @@
|
||||||
|
propellor (2.0.0) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* Property has been converted to a GADT, and will be Property NoInfo
|
||||||
|
or Property HasInfo.
|
||||||
|
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"
|
||||||
|
(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
|
||||||
|
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
|
||||||
|
up a host's properties.
|
||||||
|
* Add descriptions of how to set missing fields to --list-fields output.
|
||||||
|
* Properties now form a tree, instead of the flat list used before.
|
||||||
|
This includes the properties used inside a container.
|
||||||
|
* Fix info propigation from fallback combinator's second Property.
|
||||||
|
* Added systemd configuration properties.
|
||||||
|
* Added journald configuration properties.
|
||||||
|
* Added more network interface configuration properties.
|
||||||
|
* Implemented OS.preserveNetwork.
|
||||||
|
|
||||||
|
-- Joey Hess <id@joeyh.name> Thu, 15 Jan 2015 20:14:29 -0400
|
||||||
|
|
||||||
propellor (1.3.2) unstable; urgency=medium
|
propellor (1.3.2) unstable; urgency=medium
|
||||||
|
|
||||||
* SSHFP records are also generated for CNAMES of hosts.
|
* SSHFP records are also generated for CNAMES of hosts.
|
||||||
|
|
|
@ -18,7 +18,7 @@ Build-Depends:
|
||||||
libghc-monadcatchio-transformers-dev,
|
libghc-monadcatchio-transformers-dev,
|
||||||
Maintainer: Gergely Nagy <algernon@madhouse-project.org>
|
Maintainer: Gergely Nagy <algernon@madhouse-project.org>
|
||||||
Standards-Version: 3.9.6
|
Standards-Version: 3.9.6
|
||||||
Vcs-Git: git://git.kitenet.net/propellor
|
Vcs-Git: git://git.joeyh.name/propellor
|
||||||
Homepage: http://propellor.branchable.com/
|
Homepage: http://propellor.branchable.com/
|
||||||
|
|
||||||
Package: propellor
|
Package: propellor
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
Currently, a RevertableProperty's Properties always both HasInfo. This
|
||||||
|
means that if a Property NoInfo is updated to be a RevertableProperty, and
|
||||||
|
someplace called ensureProperty on it, that will refuse to compile.
|
||||||
|
|
||||||
|
The workaround is generally to export the original NoInfo property under
|
||||||
|
a different name, so it can still be used with ensureProperty.
|
||||||
|
|
||||||
|
This could be fixed:
|
||||||
|
|
||||||
|
data RevertableProperty i1 i2 where
|
||||||
|
RProp :: Property i1 -> Property i2 -> RevertableProperty i1 i2
|
||||||
|
|
||||||
|
However, needing to write "RevertableProperty HasInfo NoInfo" is quite
|
||||||
|
a mouthful!
|
||||||
|
|
||||||
|
Since only 2 places in the propellor source code currently need to deal
|
||||||
|
with this, it doesn't currently seem worth making the change, unless a less
|
||||||
|
intrusive way can be found.
|
||||||
|
|
||||||
|
Probably related would be to make RevertableProperty a constructor in the
|
||||||
|
Property GADT, which would allow more property combinators to work on
|
||||||
|
RevertableProperties. That would look like:
|
||||||
|
|
||||||
|
data Propety i where
|
||||||
|
...
|
||||||
|
RProp :: Property i1 -> Property i2 -> Property (CInfo i1 i2)
|
||||||
|
|
||||||
|
In this case, there's only one Info/NoInfo encompassing both sides, and
|
||||||
|
so ensureProperty could only be used on it if both sides were NoInfo.
|
|
@ -1,3 +1,5 @@
|
||||||
|
> Now [[fixed|done]]!! --[[Joey]]
|
||||||
|
|
||||||
Currently, Info about a Host's Properties is propigated to the host by
|
Currently, Info about a Host's Properties is propigated to the host by
|
||||||
examining the tree of Properties.
|
examining the tree of Properties.
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,7 @@ Library
|
||||||
Propellor.Property.Gpg
|
Propellor.Property.Gpg
|
||||||
Propellor.Property.Group
|
Propellor.Property.Group
|
||||||
Propellor.Property.Grub
|
Propellor.Property.Grub
|
||||||
|
Propellor.Property.Journald
|
||||||
Propellor.Property.Mount
|
Propellor.Property.Mount
|
||||||
Propellor.Property.Network
|
Propellor.Property.Network
|
||||||
Propellor.Property.Nginx
|
Propellor.Property.Nginx
|
||||||
|
@ -94,6 +95,7 @@ Library
|
||||||
Propellor.Property.Postfix
|
Propellor.Property.Postfix
|
||||||
Propellor.Property.Prosody
|
Propellor.Property.Prosody
|
||||||
Propellor.Property.Reboot
|
Propellor.Property.Reboot
|
||||||
|
Propellor.Property.List
|
||||||
Propellor.Property.Scheduled
|
Propellor.Property.Scheduled
|
||||||
Propellor.Property.Service
|
Propellor.Property.Service
|
||||||
Propellor.Property.Ssh
|
Propellor.Property.Ssh
|
||||||
|
@ -108,7 +110,7 @@ Library
|
||||||
Propellor.Property.SiteSpecific.GitHome
|
Propellor.Property.SiteSpecific.GitHome
|
||||||
Propellor.Property.SiteSpecific.JoeySites
|
Propellor.Property.SiteSpecific.JoeySites
|
||||||
Propellor.Property.SiteSpecific.GitAnnexBuilder
|
Propellor.Property.SiteSpecific.GitAnnexBuilder
|
||||||
Propellor.Host
|
Propellor.PropAccum
|
||||||
Propellor.CmdLine
|
Propellor.CmdLine
|
||||||
Propellor.Info
|
Propellor.Info
|
||||||
Propellor.Message
|
Propellor.Message
|
||||||
|
@ -122,6 +124,9 @@ Library
|
||||||
Propellor.Types.Empty
|
Propellor.Types.Empty
|
||||||
Propellor.Types.OS
|
Propellor.Types.OS
|
||||||
Propellor.Types.PrivData
|
Propellor.Types.PrivData
|
||||||
|
Propellor.Types.Val
|
||||||
|
Propellor.Types.Result
|
||||||
|
Propellor.Types.CmdLine
|
||||||
Other-Modules:
|
Other-Modules:
|
||||||
Propellor.Git
|
Propellor.Git
|
||||||
Propellor.Gpg
|
Propellor.Gpg
|
||||||
|
@ -133,11 +138,13 @@ Library
|
||||||
Propellor.Property.Chroot.Util
|
Propellor.Property.Chroot.Util
|
||||||
Utility.Applicative
|
Utility.Applicative
|
||||||
Utility.Data
|
Utility.Data
|
||||||
|
Utility.DataUnits
|
||||||
Utility.Directory
|
Utility.Directory
|
||||||
Utility.Env
|
Utility.Env
|
||||||
Utility.Exception
|
Utility.Exception
|
||||||
Utility.FileMode
|
Utility.FileMode
|
||||||
Utility.FileSystemEncoding
|
Utility.FileSystemEncoding
|
||||||
|
Utility.HumanNumber
|
||||||
Utility.LinuxMkLibs
|
Utility.LinuxMkLibs
|
||||||
Utility.Misc
|
Utility.Misc
|
||||||
Utility.Monad
|
Utility.Monad
|
||||||
|
|
|
@ -27,13 +27,14 @@
|
||||||
--
|
--
|
||||||
-- See config.hs for a more complete example, and clone Propellor's
|
-- See config.hs for a more complete example, and clone Propellor's
|
||||||
-- git repository for a deployable system using Propellor:
|
-- git repository for a deployable system using Propellor:
|
||||||
-- git clone <git://git.kitenet.net/propellor>
|
-- git clone <git://git.joeyh.name/propellor>
|
||||||
|
|
||||||
module Propellor (
|
module Propellor (
|
||||||
module Propellor.Types
|
module Propellor.Types
|
||||||
, module Propellor.Property
|
, module Propellor.Property
|
||||||
|
, module Propellor.Property.List
|
||||||
, module Propellor.Property.Cmd
|
, module Propellor.Property.Cmd
|
||||||
, module Propellor.Host
|
, module Propellor.PropAccum
|
||||||
, module Propellor.Info
|
, module Propellor.Info
|
||||||
, module Propellor.PrivData
|
, module Propellor.PrivData
|
||||||
, module Propellor.Types.PrivData
|
, module Propellor.Types.PrivData
|
||||||
|
@ -48,13 +49,14 @@ module Propellor (
|
||||||
import Propellor.Types
|
import Propellor.Types
|
||||||
import Propellor.Property
|
import Propellor.Property
|
||||||
import Propellor.Engine
|
import Propellor.Engine
|
||||||
|
import Propellor.Property.List
|
||||||
import Propellor.Property.Cmd
|
import Propellor.Property.Cmd
|
||||||
import Propellor.PrivData
|
import Propellor.PrivData
|
||||||
import Propellor.Types.PrivData
|
import Propellor.Types.PrivData
|
||||||
import Propellor.Message
|
import Propellor.Message
|
||||||
import Propellor.Exception
|
import Propellor.Exception
|
||||||
import Propellor.Info
|
import Propellor.Info
|
||||||
import Propellor.Host
|
import Propellor.PropAccum
|
||||||
|
|
||||||
import Utility.PartialPrelude as X
|
import Utility.PartialPrelude as X
|
||||||
import Utility.Process as X
|
import Utility.Process as X
|
||||||
|
|
|
@ -13,6 +13,7 @@ import Propellor
|
||||||
import Propellor.Gpg
|
import Propellor.Gpg
|
||||||
import Propellor.Git
|
import Propellor.Git
|
||||||
import Propellor.Spin
|
import Propellor.Spin
|
||||||
|
import Propellor.Types.CmdLine
|
||||||
import qualified Propellor.Property.Docker as Docker
|
import qualified Propellor.Property.Docker as Docker
|
||||||
import qualified Propellor.Property.Chroot as Chroot
|
import qualified Propellor.Property.Chroot as Chroot
|
||||||
import qualified Propellor.Shim as Shim
|
import qualified Propellor.Shim as Shim
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{-# LANGUAGE PackageImports #-}
|
{-# LANGUAGE PackageImports #-}
|
||||||
|
{-# LANGUAGE GADTs #-}
|
||||||
|
|
||||||
module Propellor.Engine (
|
module Propellor.Engine (
|
||||||
mainProperties,
|
mainProperties,
|
||||||
|
@ -35,7 +36,7 @@ import Utility.Monad
|
||||||
mainProperties :: Host -> IO ()
|
mainProperties :: Host -> IO ()
|
||||||
mainProperties host = do
|
mainProperties host = do
|
||||||
ret <- runPropellor host $
|
ret <- runPropellor host $
|
||||||
ensureProperties [Property "overall" (ensureProperties $ hostProperties host) mempty]
|
ensureProperties [ignoreInfo $ infoProperty "overall" (ensureProperties ps) mempty mempty]
|
||||||
h <- mkMessageHandle
|
h <- mkMessageHandle
|
||||||
whenConsole h $
|
whenConsole h $
|
||||||
setTitle "propellor: done"
|
setTitle "propellor: done"
|
||||||
|
@ -43,6 +44,8 @@ mainProperties host = do
|
||||||
case ret of
|
case ret of
|
||||||
FailedChange -> exitWith (ExitFailure 1)
|
FailedChange -> exitWith (ExitFailure 1)
|
||||||
_ -> exitWith ExitSuccess
|
_ -> exitWith ExitSuccess
|
||||||
|
where
|
||||||
|
ps = map ignoreInfo $ hostProperties host
|
||||||
|
|
||||||
-- | Runs a Propellor action with the specified host.
|
-- | Runs a Propellor action with the specified host.
|
||||||
--
|
--
|
||||||
|
@ -62,11 +65,13 @@ runEndAction host res (EndAction desc a) = actionMessageOn (hostName host) desc
|
||||||
|
|
||||||
-- | For when code running in the Propellor monad needs to ensure a
|
-- | For when code running in the Propellor monad needs to ensure a
|
||||||
-- Property.
|
-- Property.
|
||||||
ensureProperty :: Property -> Propellor Result
|
--
|
||||||
|
-- This can only be used on a Property that has NoInfo.
|
||||||
|
ensureProperty :: Property NoInfo -> Propellor Result
|
||||||
ensureProperty = catchPropellor . propertySatisfy
|
ensureProperty = catchPropellor . propertySatisfy
|
||||||
|
|
||||||
-- | Ensures a list of Properties, with a display of each as it runs.
|
-- | Ensures a list of Properties, with a display of each as it runs.
|
||||||
ensureProperties :: [Property] -> Propellor Result
|
ensureProperties :: [Property NoInfo] -> Propellor Result
|
||||||
ensureProperties ps = ensure ps NoChange
|
ensureProperties ps = ensure ps NoChange
|
||||||
where
|
where
|
||||||
ensure [] rs = return rs
|
ensure [] rs = return rs
|
||||||
|
@ -77,7 +82,7 @@ ensureProperties ps = ensure ps NoChange
|
||||||
|
|
||||||
-- | Lifts an action into a different host.
|
-- | Lifts an action into a different host.
|
||||||
--
|
--
|
||||||
-- For example, `fromHost hosts "otherhost" getPubKey`
|
-- > fromHost hosts "otherhost" getPubKey
|
||||||
fromHost :: [Host] -> HostName -> Propellor a -> Propellor (Maybe a)
|
fromHost :: [Host] -> HostName -> Propellor a -> Propellor (Maybe a)
|
||||||
fromHost l hn getter = case findHost l hn of
|
fromHost l hn getter = case findHost l hn of
|
||||||
Nothing -> return Nothing
|
Nothing -> return Nothing
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
{-# LANGUAGE PackageImports #-}
|
|
||||||
|
|
||||||
module Propellor.Host where
|
|
||||||
|
|
||||||
import Data.Monoid
|
|
||||||
import qualified Data.Set as S
|
|
||||||
|
|
||||||
import Propellor.Types
|
|
||||||
import Propellor.Info
|
|
||||||
import Propellor.Property
|
|
||||||
import Propellor.PrivData
|
|
||||||
|
|
||||||
-- | Starts accumulating the properties of a Host.
|
|
||||||
--
|
|
||||||
-- > host "example.com"
|
|
||||||
-- > & someproperty
|
|
||||||
-- > ! oldproperty
|
|
||||||
-- > & otherproperty
|
|
||||||
host :: HostName -> Host
|
|
||||||
host hn = Host hn [] mempty
|
|
||||||
|
|
||||||
-- | Something that can accumulate properties.
|
|
||||||
class Hostlike h where
|
|
||||||
-- | Adds a property.
|
|
||||||
--
|
|
||||||
-- Can add Properties and RevertableProperties
|
|
||||||
(&) :: IsProp p => h -> p -> h
|
|
||||||
|
|
||||||
-- | Like (&), but adds the property as the
|
|
||||||
-- first property of the host. Normally, property
|
|
||||||
-- order should not matter, but this is useful
|
|
||||||
-- when it does.
|
|
||||||
(&^) :: IsProp p => h -> p -> h
|
|
||||||
|
|
||||||
getHost :: h -> Host
|
|
||||||
|
|
||||||
instance Hostlike Host where
|
|
||||||
(Host hn ps is) & p = Host hn (ps ++ [toProp p]) (is <> getInfo p)
|
|
||||||
(Host hn ps is) &^ p = Host hn ([toProp p] ++ ps) (getInfo p <> is)
|
|
||||||
getHost h = h
|
|
||||||
|
|
||||||
-- | Adds a property in reverted form.
|
|
||||||
(!) :: Hostlike h => h -> RevertableProperty -> h
|
|
||||||
h ! p = h & revert p
|
|
||||||
|
|
||||||
infixl 1 &^
|
|
||||||
infixl 1 &
|
|
||||||
infixl 1 !
|
|
||||||
|
|
||||||
-- | When eg, docking a container, some of the Info about the container
|
|
||||||
-- should propigate out to the Host it's on. This includes DNS info,
|
|
||||||
-- so that eg, aliases of the container are reflected in the dns for the
|
|
||||||
-- host where it runs.
|
|
||||||
--
|
|
||||||
-- This adjusts the Property that docks a container, to include such info
|
|
||||||
-- from the container.
|
|
||||||
propigateInfo :: Hostlike hl => hl -> Property -> (Info -> Info) -> Property
|
|
||||||
propigateInfo hl p f = combineProperties (propertyDesc p) $
|
|
||||||
p' : dnsprops ++ privprops
|
|
||||||
where
|
|
||||||
p' = p { propertyInfo = f (propertyInfo p) }
|
|
||||||
i = hostInfo (getHost hl)
|
|
||||||
dnsprops = map addDNS (S.toList $ _dns i)
|
|
||||||
privprops = map addPrivDataField (S.toList $ _privDataFields i)
|
|
|
@ -3,6 +3,7 @@
|
||||||
module Propellor.Info where
|
module Propellor.Info where
|
||||||
|
|
||||||
import Propellor.Types
|
import Propellor.Types
|
||||||
|
import Propellor.Types.Val
|
||||||
|
|
||||||
import "mtl" Control.Monad.Reader
|
import "mtl" Control.Monad.Reader
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
|
@ -11,13 +12,13 @@ import Data.Maybe
|
||||||
import Data.Monoid
|
import Data.Monoid
|
||||||
import Control.Applicative
|
import Control.Applicative
|
||||||
|
|
||||||
pureInfoProperty :: Desc -> Info -> Property
|
pureInfoProperty :: Desc -> Info -> Property HasInfo
|
||||||
pureInfoProperty desc = Property ("has " ++ desc) (return NoChange)
|
pureInfoProperty desc i = infoProperty ("has " ++ desc) (return NoChange) i mempty
|
||||||
|
|
||||||
askInfo :: (Info -> Val a) -> Propellor (Maybe a)
|
askInfo :: (Info -> Val a) -> Propellor (Maybe a)
|
||||||
askInfo f = asks (fromVal . f . hostInfo)
|
askInfo f = asks (fromVal . f . hostInfo)
|
||||||
|
|
||||||
os :: System -> Property
|
os :: System -> Property HasInfo
|
||||||
os system = pureInfoProperty ("Operating " ++ show system) $
|
os system = pureInfoProperty ("Operating " ++ show system) $
|
||||||
mempty { _os = Val system }
|
mempty { _os = Val system }
|
||||||
|
|
||||||
|
@ -33,11 +34,11 @@ getOS = askInfo _os
|
||||||
-- When propellor --spin is used to deploy a host, it checks
|
-- 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
|
-- 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.
|
-- out of date, the host will instead be contacted directly by IP address.
|
||||||
ipv4 :: String -> Property
|
ipv4 :: String -> Property HasInfo
|
||||||
ipv4 = addDNS . Address . IPv4
|
ipv4 = addDNS . Address . IPv4
|
||||||
|
|
||||||
-- | Indidate that a host has an AAAA record in the DNS.
|
-- | Indidate that a host has an AAAA record in the DNS.
|
||||||
ipv6 :: String -> Property
|
ipv6 :: String -> Property HasInfo
|
||||||
ipv6 = addDNS . Address . IPv6
|
ipv6 = addDNS . Address . IPv6
|
||||||
|
|
||||||
-- | Indicates another name for the host in the DNS.
|
-- | Indicates another name for the host in the DNS.
|
||||||
|
@ -46,7 +47,7 @@ ipv6 = addDNS . Address . IPv6
|
||||||
-- to use their address, rather than using a CNAME. This avoids various
|
-- to use their address, rather than using a CNAME. This avoids various
|
||||||
-- problems with CNAMEs, and also means that when multiple hosts have the
|
-- problems with CNAMEs, and also means that when multiple hosts have the
|
||||||
-- same alias, a DNS round-robin is automatically set up.
|
-- same alias, a DNS round-robin is automatically set up.
|
||||||
alias :: Domain -> Property
|
alias :: Domain -> Property HasInfo
|
||||||
alias d = pureInfoProperty ("alias " ++ d) $ mempty
|
alias d = pureInfoProperty ("alias " ++ d) $ mempty
|
||||||
{ _aliases = S.singleton d
|
{ _aliases = S.singleton d
|
||||||
-- A CNAME is added here, but the DNS setup code converts it to an
|
-- A CNAME is added here, but the DNS setup code converts it to an
|
||||||
|
@ -54,7 +55,7 @@ alias d = pureInfoProperty ("alias " ++ d) $ mempty
|
||||||
, _dns = S.singleton $ CNAME $ AbsDomain d
|
, _dns = S.singleton $ CNAME $ AbsDomain d
|
||||||
}
|
}
|
||||||
|
|
||||||
addDNS :: Record -> Property
|
addDNS :: Record -> Property HasInfo
|
||||||
addDNS r = pureInfoProperty (rdesc r) $ mempty { _dns = S.singleton r }
|
addDNS r = pureInfoProperty (rdesc r) $ mempty { _dns = S.singleton r }
|
||||||
where
|
where
|
||||||
rdesc (CNAME d) = unwords ["alias", ddesc d]
|
rdesc (CNAME d) = unwords ["alias", ddesc d]
|
||||||
|
|
|
@ -1,6 +1,19 @@
|
||||||
{-# LANGUAGE PackageImports #-}
|
{-# LANGUAGE PackageImports #-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
|
||||||
module Propellor.PrivData where
|
module Propellor.PrivData (
|
||||||
|
withPrivData,
|
||||||
|
withSomePrivData,
|
||||||
|
addPrivData,
|
||||||
|
setPrivData,
|
||||||
|
dumpPrivData,
|
||||||
|
editPrivData,
|
||||||
|
filterPrivData,
|
||||||
|
listPrivDataFields,
|
||||||
|
makePrivDataDir,
|
||||||
|
decryptPrivData,
|
||||||
|
PrivMap,
|
||||||
|
) where
|
||||||
|
|
||||||
import Control.Applicative
|
import Control.Applicative
|
||||||
import System.IO
|
import System.IO
|
||||||
|
@ -48,29 +61,29 @@ import Utility.Table
|
||||||
-- being used, which is necessary to ensure that the privdata is sent to
|
-- being used, which is necessary to ensure that the privdata is sent to
|
||||||
-- the remote host by propellor.
|
-- the remote host by propellor.
|
||||||
withPrivData
|
withPrivData
|
||||||
:: (IsContext c, IsPrivDataSource s)
|
:: (IsContext c, IsPrivDataSource s, IsProp (Property i))
|
||||||
=> s
|
=> s
|
||||||
-> c
|
-> c
|
||||||
-> (((PrivData -> Propellor Result) -> Propellor Result) -> Property)
|
-> (((PrivData -> Propellor Result) -> Propellor Result) -> Property i)
|
||||||
-> Property
|
-> Property HasInfo
|
||||||
withPrivData s = withPrivData' snd [s]
|
withPrivData s = withPrivData' snd [s]
|
||||||
|
|
||||||
-- Like withPrivData, but here any one of a list of PrivDataFields can be used.
|
-- Like withPrivData, but here any one of a list of PrivDataFields can be used.
|
||||||
withSomePrivData
|
withSomePrivData
|
||||||
:: (IsContext c, IsPrivDataSource s)
|
:: (IsContext c, IsPrivDataSource s, IsProp (Property i))
|
||||||
=> [s]
|
=> [s]
|
||||||
-> c
|
-> c
|
||||||
-> ((((PrivDataField, PrivData) -> Propellor Result) -> Propellor Result) -> Property)
|
-> ((((PrivDataField, PrivData) -> Propellor Result) -> Propellor Result) -> Property i)
|
||||||
-> Property
|
-> Property HasInfo
|
||||||
withSomePrivData = withPrivData' id
|
withSomePrivData = withPrivData' id
|
||||||
|
|
||||||
withPrivData'
|
withPrivData'
|
||||||
:: (IsContext c, IsPrivDataSource s)
|
:: (IsContext c, IsPrivDataSource s, IsProp (Property i))
|
||||||
=> ((PrivDataField, PrivData) -> v)
|
=> ((PrivDataField, PrivData) -> v)
|
||||||
-> [s]
|
-> [s]
|
||||||
-> c
|
-> c
|
||||||
-> (((v -> Propellor Result) -> Propellor Result) -> Property)
|
-> (((v -> Propellor Result) -> Propellor Result) -> Property i)
|
||||||
-> Property
|
-> Property HasInfo
|
||||||
withPrivData' feed srclist c mkprop = addinfo $ mkprop $ \a ->
|
withPrivData' feed srclist c mkprop = addinfo $ mkprop $ \a ->
|
||||||
maybe missing (a . feed) =<< getM get fieldlist
|
maybe missing (a . feed) =<< getM get fieldlist
|
||||||
where
|
where
|
||||||
|
@ -82,20 +95,28 @@ withPrivData' feed srclist c mkprop = addinfo $ mkprop $ \a ->
|
||||||
Context cname <- mkHostContext hc <$> asks hostName
|
Context cname <- mkHostContext hc <$> asks hostName
|
||||||
warningMessage $ "Missing privdata " ++ intercalate " or " fieldnames ++ " (for " ++ cname ++ ")"
|
warningMessage $ "Missing privdata " ++ intercalate " or " fieldnames ++ " (for " ++ cname ++ ")"
|
||||||
liftIO $ putStrLn $ "Fix this by running:"
|
liftIO $ putStrLn $ "Fix this by running:"
|
||||||
liftIO $ forM_ srclist $ \src -> do
|
liftIO $ showSet $
|
||||||
putStrLn $ " propellor --set '" ++ show (privDataField src) ++ "' '" ++ cname ++ "' \\"
|
map (\s -> (privDataField s, Context cname, describePrivDataSource s)) srclist
|
||||||
maybe noop (\d -> putStrLn $ " " ++ d) (describePrivDataSource src)
|
|
||||||
putStrLn ""
|
|
||||||
return FailedChange
|
return FailedChange
|
||||||
addinfo p = p { propertyInfo = propertyInfo p <> mempty { _privDataFields = fieldset } }
|
addinfo p = infoProperty
|
||||||
|
(propertyDesc p)
|
||||||
|
(propertySatisfy p)
|
||||||
|
(propertyInfo p <> mempty { _privData = privset })
|
||||||
|
(propertyChildren p)
|
||||||
|
privset = S.fromList $ map (\s -> (privDataField s, describePrivDataSource s, hc)) srclist
|
||||||
fieldnames = map show fieldlist
|
fieldnames = map show fieldlist
|
||||||
fieldset = S.fromList $ zip fieldlist (repeat hc)
|
|
||||||
fieldlist = map privDataField srclist
|
fieldlist = map privDataField srclist
|
||||||
hc = asHostContext c
|
hc = asHostContext c
|
||||||
|
|
||||||
addPrivDataField :: (PrivDataField, HostContext) -> Property
|
showSet :: [(PrivDataField, Context, Maybe PrivDataSourceDesc)] -> IO ()
|
||||||
addPrivDataField v = pureInfoProperty (show v) $
|
showSet l = forM_ l $ \(f, Context c, md) -> do
|
||||||
mempty { _privDataFields = S.singleton v }
|
putStrLn $ " propellor --set '" ++ show f ++ "' '" ++ c ++ "' \\"
|
||||||
|
maybe noop (\d -> putStrLn $ " " ++ d) md
|
||||||
|
putStrLn ""
|
||||||
|
|
||||||
|
addPrivData :: (PrivDataField, Maybe PrivDataSourceDesc, HostContext) -> Property HasInfo
|
||||||
|
addPrivData v = pureInfoProperty (show v) $
|
||||||
|
mempty { _privData = S.singleton v }
|
||||||
|
|
||||||
{- Gets the requested field's value, in the specified context if it's
|
{- Gets the requested field's value, in the specified context if it's
|
||||||
- available, from the host's local privdata cache. -}
|
- available, from the host's local privdata cache. -}
|
||||||
|
@ -107,12 +128,12 @@ getLocalPrivData field context =
|
||||||
|
|
||||||
type PrivMap = M.Map (PrivDataField, Context) PrivData
|
type PrivMap = M.Map (PrivDataField, Context) PrivData
|
||||||
|
|
||||||
{- Get only the set of PrivData that the Host's Info says it uses. -}
|
-- | Get only the set of PrivData that the Host's Info says it uses.
|
||||||
filterPrivData :: Host -> PrivMap -> PrivMap
|
filterPrivData :: Host -> PrivMap -> PrivMap
|
||||||
filterPrivData host = M.filterWithKey (\k _v -> S.member k used)
|
filterPrivData host = M.filterWithKey (\k _v -> S.member k used)
|
||||||
where
|
where
|
||||||
used = S.map (\(f, c) -> (f, mkHostContext c (hostName host))) $
|
used = S.map (\(f, _, c) -> (f, mkHostContext c (hostName host))) $
|
||||||
_privDataFields $ hostInfo host
|
_privData $ hostInfo host
|
||||||
|
|
||||||
getPrivData :: PrivDataField -> Context -> PrivMap -> Maybe PrivData
|
getPrivData :: PrivDataField -> Context -> PrivMap -> Maybe PrivData
|
||||||
getPrivData field context = M.lookup (field, context)
|
getPrivData field context = M.lookup (field, context)
|
||||||
|
@ -142,10 +163,17 @@ editPrivData field context = do
|
||||||
listPrivDataFields :: [Host] -> IO ()
|
listPrivDataFields :: [Host] -> IO ()
|
||||||
listPrivDataFields hosts = do
|
listPrivDataFields hosts = do
|
||||||
m <- decryptPrivData
|
m <- decryptPrivData
|
||||||
showtable "Currently set data:" $
|
|
||||||
map mkrow (M.keys m)
|
section "Currently set data:"
|
||||||
showtable "Data that would be used if set:" $
|
showtable $ map mkrow (M.keys m)
|
||||||
map mkrow (M.keys $ M.difference wantedmap m)
|
let missing = M.keys $ M.difference wantedmap m
|
||||||
|
|
||||||
|
unless (null missing) $ do
|
||||||
|
section "Missing data that would be used if set:"
|
||||||
|
showtable $ map mkrow missing
|
||||||
|
|
||||||
|
section "How to set missing data:"
|
||||||
|
showSet $ map (\(f, c) -> (f, c, join $ M.lookup (f, c) descmap)) missing
|
||||||
where
|
where
|
||||||
header = ["Field", "Context", "Used by"]
|
header = ["Field", "Context", "Used by"]
|
||||||
mkrow k@(field, (Context context)) =
|
mkrow k@(field, (Context context)) =
|
||||||
|
@ -153,12 +181,13 @@ listPrivDataFields hosts = do
|
||||||
, shellEscape context
|
, shellEscape context
|
||||||
, intercalate ", " $ sort $ fromMaybe [] $ M.lookup k usedby
|
, intercalate ", " $ sort $ fromMaybe [] $ M.lookup k usedby
|
||||||
]
|
]
|
||||||
mkhostmap host = M.fromList $ map (\(f, c) -> ((f, mkHostContext c (hostName host)), [hostName host])) $
|
mkhostmap host mkv = M.fromList $ map (\(f, d, c) -> ((f, mkHostContext c (hostName host)), mkv d)) $
|
||||||
S.toList $ _privDataFields $ hostInfo host
|
S.toList $ _privData $ hostInfo host
|
||||||
usedby = M.unionsWith (++) $ map mkhostmap hosts
|
usedby = M.unionsWith (++) $ map (\h -> mkhostmap h $ const $ [hostName h]) hosts
|
||||||
wantedmap = M.fromList $ zip (M.keys usedby) (repeat "")
|
wantedmap = M.fromList $ zip (M.keys usedby) (repeat "")
|
||||||
showtable desc rows = do
|
descmap = M.unions $ map (\h -> mkhostmap h id) hosts
|
||||||
putStrLn $ "\n" ++ desc
|
section desc = putStrLn $ "\n" ++ desc
|
||||||
|
showtable rows = do
|
||||||
putStr $ unlines $ formatTable $ tableWithHeader header rows
|
putStr $ unlines $ formatTable $ tableWithHeader header rows
|
||||||
|
|
||||||
setPrivDataTo :: PrivDataField -> Context -> PrivData -> IO ()
|
setPrivDataTo :: PrivDataField -> Context -> PrivData -> IO ()
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
{-# LANGUAGE PackageImports #-}
|
||||||
|
|
||||||
|
module Propellor.PropAccum where
|
||||||
|
|
||||||
|
import Data.Monoid
|
||||||
|
|
||||||
|
import Propellor.Types
|
||||||
|
import Propellor.Property
|
||||||
|
|
||||||
|
-- | Starts accumulating the properties of a Host.
|
||||||
|
--
|
||||||
|
-- > host "example.com"
|
||||||
|
-- > & someproperty
|
||||||
|
-- > ! oldproperty
|
||||||
|
-- > & otherproperty
|
||||||
|
host :: HostName -> Host
|
||||||
|
host hn = Host hn [] mempty
|
||||||
|
|
||||||
|
-- | Starts accumulating a list of properties.
|
||||||
|
--
|
||||||
|
-- > propertyList "foo" $ props
|
||||||
|
-- > & someproperty
|
||||||
|
-- > ! oldproperty
|
||||||
|
-- > & otherproperty
|
||||||
|
props :: PropList
|
||||||
|
props = PropList []
|
||||||
|
|
||||||
|
-- | Something that can accumulate properties.
|
||||||
|
class PropAccum h where
|
||||||
|
-- | Adds a property.
|
||||||
|
--
|
||||||
|
-- Can add Properties and RevertableProperties
|
||||||
|
(&) :: IsProp p => h -> p -> h
|
||||||
|
|
||||||
|
-- | Like (&), but adds the property at the front of the list.
|
||||||
|
(&^) :: IsProp p => h -> p -> h
|
||||||
|
|
||||||
|
getProperties :: h -> [Property HasInfo]
|
||||||
|
|
||||||
|
instance PropAccum Host where
|
||||||
|
(Host hn ps is) & p = Host hn (ps ++ [toProp p])
|
||||||
|
(is <> getInfoRecursive p)
|
||||||
|
(Host hn ps is) &^ p = Host hn ([toProp p] ++ ps)
|
||||||
|
(getInfoRecursive p <> is)
|
||||||
|
getProperties = hostProperties
|
||||||
|
|
||||||
|
data PropList = PropList [Property HasInfo]
|
||||||
|
|
||||||
|
instance PropAccum PropList where
|
||||||
|
PropList l & p = PropList (l ++ [toProp p])
|
||||||
|
PropList l &^ p = PropList ([toProp p] ++ l)
|
||||||
|
getProperties (PropList l) = l
|
||||||
|
|
||||||
|
-- | Adds a property in reverted form.
|
||||||
|
(!) :: PropAccum h => h -> RevertableProperty -> h
|
||||||
|
h ! p = h & revert p
|
||||||
|
|
||||||
|
infixl 1 &^
|
||||||
|
infixl 1 &
|
||||||
|
infixl 1 !
|
||||||
|
|
||||||
|
-- | 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 propigated out to the Property.
|
||||||
|
--
|
||||||
|
-- DNS Info is propigated, so that eg, aliases of a PropAccum
|
||||||
|
-- are reflected in the dns for the host where it runs.
|
||||||
|
--
|
||||||
|
-- PrivData Info is propigated, so that properties used inside a
|
||||||
|
-- PropAccum will have the necessary PrivData available.
|
||||||
|
propigateContainer
|
||||||
|
:: (PropAccum container)
|
||||||
|
=> container
|
||||||
|
-> Property HasInfo
|
||||||
|
-> Property HasInfo
|
||||||
|
propigateContainer c prop = infoProperty
|
||||||
|
(propertyDesc prop)
|
||||||
|
(propertySatisfy prop)
|
||||||
|
(propertyInfo prop)
|
||||||
|
(propertyChildren prop ++ hostprops)
|
||||||
|
where
|
||||||
|
hostprops = map go $ getProperties c
|
||||||
|
go p =
|
||||||
|
let i = propertyInfo p
|
||||||
|
i' = mempty
|
||||||
|
{ _dns = _dns i
|
||||||
|
, _privData = _privData i
|
||||||
|
}
|
||||||
|
cs = map go (propertyChildren p)
|
||||||
|
in infoProperty (propertyDesc p) (propertySatisfy p) i' cs
|
|
@ -1,4 +1,5 @@
|
||||||
{-# LANGUAGE PackageImports #-}
|
{-# LANGUAGE PackageImports #-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
|
||||||
module Propellor.Property where
|
module Propellor.Property where
|
||||||
|
|
||||||
|
@ -11,47 +12,21 @@ import "mtl" Control.Monad.RWS.Strict
|
||||||
|
|
||||||
import Propellor.Types
|
import Propellor.Types
|
||||||
import Propellor.Info
|
import Propellor.Info
|
||||||
import Propellor.Engine
|
|
||||||
import Utility.Monad
|
import Utility.Monad
|
||||||
|
|
||||||
-- Constructs a Property.
|
-- | Constructs a Property, from a description and an action to run to
|
||||||
property :: Desc -> Propellor Result -> Property
|
-- ensure the Property is met.
|
||||||
property d s = Property d s mempty
|
property :: Desc -> Propellor Result -> Property NoInfo
|
||||||
|
property d s = simpleProperty d s mempty
|
||||||
-- | 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 propigate overall success/failure.
|
|
||||||
propertyList :: Desc -> [Property] -> Property
|
|
||||||
propertyList desc ps = Property desc (ensureProperties ps) (combineInfos ps)
|
|
||||||
|
|
||||||
-- | Combines a list of properties, resulting in one property that
|
|
||||||
-- ensures each in turn. Stops if a property fails.
|
|
||||||
combineProperties :: Desc -> [Property] -> Property
|
|
||||||
combineProperties desc ps = Property desc (go ps NoChange) (combineInfos ps)
|
|
||||||
where
|
|
||||||
go [] rs = return rs
|
|
||||||
go (l:ls) rs = do
|
|
||||||
r <- ensureProperty l
|
|
||||||
case r of
|
|
||||||
FailedChange -> return FailedChange
|
|
||||||
_ -> go ls (r <> rs)
|
|
||||||
|
|
||||||
-- | Combines together two properties, resulting in one property
|
|
||||||
-- that ensures the first, and if the first succeeds, ensures the second.
|
|
||||||
-- The property uses the description of the first property.
|
|
||||||
before :: Property -> Property -> Property
|
|
||||||
p1 `before` p2 = p2 `requires` p1
|
|
||||||
`describe` (propertyDesc p1)
|
|
||||||
|
|
||||||
-- | Makes a perhaps non-idempotent Property be idempotent by using a flag
|
-- | Makes a perhaps non-idempotent Property be idempotent by using a flag
|
||||||
-- file to indicate whether it has run before.
|
-- file to indicate whether it has run before.
|
||||||
-- Use with caution.
|
-- Use with caution.
|
||||||
flagFile :: Property -> FilePath -> Property
|
flagFile :: Property i -> FilePath -> Property i
|
||||||
flagFile p = flagFile' p . return
|
flagFile p = flagFile' p . return
|
||||||
|
|
||||||
flagFile' :: Property -> IO FilePath -> Property
|
flagFile' :: Property i -> IO FilePath -> Property i
|
||||||
flagFile' p getflagfile = adjustProperty p $ \satisfy -> do
|
flagFile' p getflagfile = adjustPropertySatisfy p $ \satisfy -> do
|
||||||
flagfile <- liftIO getflagfile
|
flagfile <- liftIO getflagfile
|
||||||
go satisfy flagfile =<< liftIO (doesFileExist flagfile)
|
go satisfy flagfile =<< liftIO (doesFileExist flagfile)
|
||||||
where
|
where
|
||||||
|
@ -64,37 +39,40 @@ flagFile' p getflagfile = adjustProperty p $ \satisfy -> do
|
||||||
writeFile flagfile ""
|
writeFile flagfile ""
|
||||||
return r
|
return r
|
||||||
|
|
||||||
--- | Whenever a change has to be made for a Property, causes a hook
|
-- | Whenever a change has to be made for a Property, causes a hook
|
||||||
-- Property to also be run, but not otherwise.
|
-- Property to also be run, but not otherwise.
|
||||||
onChange :: Property -> Property -> Property
|
onChange
|
||||||
p `onChange` hook = Property (propertyDesc p) satisfy (combineInfo p hook)
|
:: (Combines (Property x) (Property y))
|
||||||
where
|
=> Property x
|
||||||
satisfy = do
|
-> Property y
|
||||||
r <- ensureProperty p
|
-> CombinedType (Property x) (Property y)
|
||||||
case r of
|
onChange = combineWith $ \p hook -> do
|
||||||
MadeChange -> do
|
r <- p
|
||||||
r' <- ensureProperty hook
|
case r of
|
||||||
return $ r <> r'
|
MadeChange -> do
|
||||||
_ -> return r
|
r' <- hook
|
||||||
|
return $ r <> r'
|
||||||
|
_ -> return r
|
||||||
|
|
||||||
(==>) :: Desc -> Property -> Property
|
-- | Alias for @flip describe@
|
||||||
|
(==>) :: IsProp (Property i) => Desc -> Property i -> Property i
|
||||||
(==>) = flip describe
|
(==>) = flip describe
|
||||||
infixl 1 ==>
|
infixl 1 ==>
|
||||||
|
|
||||||
-- | Makes a Property only need to do anything when a test succeeds.
|
-- | Makes a Property only need to do anything when a test succeeds.
|
||||||
check :: IO Bool -> Property -> Property
|
check :: IO Bool -> Property i -> Property i
|
||||||
check c p = adjustProperty p $ \satisfy -> ifM (liftIO c)
|
check c p = adjustPropertySatisfy p $ \satisfy -> ifM (liftIO c)
|
||||||
( satisfy
|
( satisfy
|
||||||
, return NoChange
|
, return NoChange
|
||||||
)
|
)
|
||||||
|
|
||||||
-- | Tries the first property, but if it fails to work, instead uses
|
-- | Tries the first property, but if it fails to work, instead uses
|
||||||
-- the second.
|
-- the second.
|
||||||
fallback :: Property -> Property -> Property
|
fallback :: (Combines (Property p1) (Property p2)) => Property p1 -> Property p2 -> Property (CInfo p1 p2)
|
||||||
fallback p1 p2 = adjustProperty p1 $ \satisfy -> do
|
fallback = combineWith $ \a1 a2 -> do
|
||||||
r <- satisfy
|
r <- a1
|
||||||
if r == FailedChange
|
if r == FailedChange
|
||||||
then propertySatisfy p2
|
then a2
|
||||||
else return r
|
else return r
|
||||||
|
|
||||||
-- | Marks a Property as trivial. It can only return FailedChange or
|
-- | Marks a Property as trivial. It can only return FailedChange or
|
||||||
|
@ -103,44 +81,33 @@ fallback p1 p2 = adjustProperty p1 $ \satisfy -> do
|
||||||
-- Useful when it's just as expensive to check if a change needs
|
-- Useful when it's just as expensive to check if a change needs
|
||||||
-- to be made as it is to just idempotently assure the property is
|
-- to be made as it is to just idempotently assure the property is
|
||||||
-- satisfied. For example, chmodding a file.
|
-- satisfied. For example, chmodding a file.
|
||||||
trivial :: Property -> Property
|
trivial :: Property i -> Property i
|
||||||
trivial p = adjustProperty p $ \satisfy -> do
|
trivial p = adjustPropertySatisfy p $ \satisfy -> do
|
||||||
r <- satisfy
|
r <- satisfy
|
||||||
if r == MadeChange
|
if r == MadeChange
|
||||||
then return NoChange
|
then return NoChange
|
||||||
else return r
|
else return r
|
||||||
|
|
||||||
doNothing :: Property
|
|
||||||
doNothing = property "noop property" noChange
|
|
||||||
|
|
||||||
-- | Makes a property that is satisfied differently depending on the host's
|
-- | Makes a property that is satisfied differently depending on the host's
|
||||||
-- operating system.
|
-- operating system.
|
||||||
--
|
--
|
||||||
-- Note that the operating system may not be declared for some hosts.
|
-- Note that the operating system may not be declared for some hosts.
|
||||||
withOS :: Desc -> (Maybe System -> Propellor Result) -> Property
|
withOS :: Desc -> (Maybe System -> Propellor Result) -> Property NoInfo
|
||||||
withOS desc a = property desc $ a =<< getOS
|
withOS desc a = property desc $ a =<< getOS
|
||||||
|
|
||||||
-- | Undoes the effect of a property.
|
-- | Undoes the effect of a property.
|
||||||
revert :: RevertableProperty -> RevertableProperty
|
revert :: RevertableProperty -> RevertableProperty
|
||||||
revert (RevertableProperty p1 p2) = RevertableProperty p2 p1
|
revert (RevertableProperty p1 p2) = RevertableProperty p2 p1
|
||||||
|
|
||||||
-- | Changes the action that is performed to satisfy a property.
|
|
||||||
adjustProperty :: Property -> (Propellor Result -> Propellor Result) -> Property
|
|
||||||
adjustProperty p f = p { propertySatisfy = f (propertySatisfy p) }
|
|
||||||
|
|
||||||
-- | Combines the Info of two properties.
|
|
||||||
combineInfo :: (IsProp p, IsProp q) => p -> q -> Info
|
|
||||||
combineInfo p q = getInfo p <> getInfo q
|
|
||||||
|
|
||||||
combineInfos :: IsProp p => [p] -> Info
|
|
||||||
combineInfos = mconcat . map getInfo
|
|
||||||
|
|
||||||
makeChange :: IO () -> Propellor Result
|
makeChange :: IO () -> Propellor Result
|
||||||
makeChange a = liftIO a >> return MadeChange
|
makeChange a = liftIO a >> return MadeChange
|
||||||
|
|
||||||
noChange :: Propellor Result
|
noChange :: Propellor Result
|
||||||
noChange = return NoChange
|
noChange = return NoChange
|
||||||
|
|
||||||
|
doNothing :: Property NoInfo
|
||||||
|
doNothing = property "noop property" noChange
|
||||||
|
|
||||||
-- | Registers an action that should be run at the very end,
|
-- | Registers an action that should be run at the very end,
|
||||||
endAction :: Desc -> (Result -> Propellor Result) -> Propellor ()
|
endAction :: Desc -> (Result -> Propellor Result) -> Propellor ()
|
||||||
endAction desc a = tell [EndAction desc a]
|
endAction desc a = tell [EndAction desc a]
|
||||||
|
|
|
@ -9,7 +9,7 @@ import Utility.SafeCommand
|
||||||
type ConfigFile = [String]
|
type ConfigFile = [String]
|
||||||
|
|
||||||
siteEnabled :: HostName -> ConfigFile -> RevertableProperty
|
siteEnabled :: HostName -> ConfigFile -> RevertableProperty
|
||||||
siteEnabled hn cf = RevertableProperty enable disable
|
siteEnabled hn cf = enable <!> disable
|
||||||
where
|
where
|
||||||
enable = combineProperties ("apache site enabled " ++ hn)
|
enable = combineProperties ("apache site enabled " ++ hn)
|
||||||
[ siteAvailable hn cf
|
[ siteAvailable hn cf
|
||||||
|
@ -28,14 +28,14 @@ siteEnabled hn cf = RevertableProperty enable disable
|
||||||
`onChange` reloaded
|
`onChange` reloaded
|
||||||
isenabled = boolSystem "a2query" [Param "-q", Param "-s", Param hn]
|
isenabled = boolSystem "a2query" [Param "-q", Param "-s", Param hn]
|
||||||
|
|
||||||
siteAvailable :: HostName -> ConfigFile -> Property
|
siteAvailable :: HostName -> ConfigFile -> Property NoInfo
|
||||||
siteAvailable hn cf = combineProperties ("apache site available " ++ hn) $
|
siteAvailable hn cf = combineProperties ("apache site available " ++ hn) $
|
||||||
map (`File.hasContent` (comment:cf)) (siteCfg hn)
|
map (`File.hasContent` (comment:cf)) (siteCfg hn)
|
||||||
where
|
where
|
||||||
comment = "# deployed with propellor, do not modify"
|
comment = "# deployed with propellor, do not modify"
|
||||||
|
|
||||||
modEnabled :: String -> RevertableProperty
|
modEnabled :: String -> RevertableProperty
|
||||||
modEnabled modname = RevertableProperty enable disable
|
modEnabled modname = enable <!> disable
|
||||||
where
|
where
|
||||||
enable = check (not <$> isenabled) $
|
enable = check (not <$> isenabled) $
|
||||||
cmdProperty "a2enmod" ["--quiet", modname]
|
cmdProperty "a2enmod" ["--quiet", modname]
|
||||||
|
@ -59,18 +59,18 @@ siteCfg hn =
|
||||||
, "/etc/apache2/sites-available/" ++ hn ++ ".conf"
|
, "/etc/apache2/sites-available/" ++ hn ++ ".conf"
|
||||||
]
|
]
|
||||||
|
|
||||||
installed :: Property
|
installed :: Property NoInfo
|
||||||
installed = Apt.installed ["apache2"]
|
installed = Apt.installed ["apache2"]
|
||||||
|
|
||||||
restarted :: Property
|
restarted :: Property NoInfo
|
||||||
restarted = Service.restarted "apache2"
|
restarted = Service.restarted "apache2"
|
||||||
|
|
||||||
reloaded :: Property
|
reloaded :: Property NoInfo
|
||||||
reloaded = Service.reloaded "apache2"
|
reloaded = Service.reloaded "apache2"
|
||||||
|
|
||||||
-- | Configure apache to use SNI to differentiate between
|
-- | Configure apache to use SNI to differentiate between
|
||||||
-- https hosts.
|
-- https hosts.
|
||||||
multiSSL :: Property
|
multiSSL :: Property NoInfo
|
||||||
multiSSL = "/etc/apache2/conf.d/ssl" `File.hasContent`
|
multiSSL = "/etc/apache2/conf.d/ssl" `File.hasContent`
|
||||||
[ "NameVirtualHost *:443"
|
[ "NameVirtualHost *:443"
|
||||||
, "SSLStrictSNIVHostCheck off"
|
, "SSLStrictSNIVHostCheck off"
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
|
||||||
module Propellor.Property.Apt where
|
module Propellor.Property.Apt where
|
||||||
|
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
|
@ -77,36 +79,36 @@ securityUpdates suite
|
||||||
--
|
--
|
||||||
-- Since the CDN is sometimes unreliable, also adds backup lines using
|
-- Since the CDN is sometimes unreliable, also adds backup lines using
|
||||||
-- kernel.org.
|
-- kernel.org.
|
||||||
stdSourcesList :: Property
|
stdSourcesList :: Property NoInfo
|
||||||
stdSourcesList = withOS ("standard sources.list") $ \o ->
|
stdSourcesList = withOS ("standard sources.list") $ \o ->
|
||||||
case o of
|
case o of
|
||||||
(Just (System (Debian suite) _)) ->
|
(Just (System (Debian suite) _)) ->
|
||||||
ensureProperty $ stdSourcesListFor suite
|
ensureProperty $ stdSourcesListFor suite
|
||||||
_ -> error "os is not declared to be Debian"
|
_ -> error "os is not declared to be Debian"
|
||||||
|
|
||||||
stdSourcesListFor :: DebianSuite -> Property
|
stdSourcesListFor :: DebianSuite -> Property NoInfo
|
||||||
stdSourcesListFor suite = stdSourcesList' suite []
|
stdSourcesListFor suite = stdSourcesList' suite []
|
||||||
|
|
||||||
-- | Adds additional sources.list generators.
|
-- | Adds additional sources.list generators.
|
||||||
--
|
--
|
||||||
-- Note that if a Property needs to enable an apt source, it's better
|
-- 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/>
|
-- to do so via a separate file in </etc/apt/sources.list.d/>
|
||||||
stdSourcesList' :: DebianSuite -> [SourcesGenerator] -> Property
|
stdSourcesList' :: DebianSuite -> [SourcesGenerator] -> Property NoInfo
|
||||||
stdSourcesList' suite more = setSourcesList
|
stdSourcesList' suite more = setSourcesList
|
||||||
(concatMap (\gen -> gen suite) generators)
|
(concatMap (\gen -> gen suite) generators)
|
||||||
`describe` ("standard sources.list for " ++ show suite)
|
`describe` ("standard sources.list for " ++ show suite)
|
||||||
where
|
where
|
||||||
generators = [debCdn, kernelOrg, securityUpdates] ++ more
|
generators = [debCdn, kernelOrg, securityUpdates] ++ more
|
||||||
|
|
||||||
setSourcesList :: [Line] -> Property
|
setSourcesList :: [Line] -> Property NoInfo
|
||||||
setSourcesList ls = sourcesList `File.hasContent` ls `onChange` update
|
setSourcesList ls = sourcesList `File.hasContent` ls `onChange` update
|
||||||
|
|
||||||
setSourcesListD :: [Line] -> FilePath -> Property
|
setSourcesListD :: [Line] -> FilePath -> Property NoInfo
|
||||||
setSourcesListD ls basename = f `File.hasContent` ls `onChange` update
|
setSourcesListD ls basename = f `File.hasContent` ls `onChange` update
|
||||||
where
|
where
|
||||||
f = "/etc/apt/sources.list.d/" ++ basename ++ ".list"
|
f = "/etc/apt/sources.list.d/" ++ basename ++ ".list"
|
||||||
|
|
||||||
runApt :: [String] -> Property
|
runApt :: [String] -> Property NoInfo
|
||||||
runApt ps = cmdProperty' "apt-get" ps noninteractiveEnv
|
runApt ps = cmdProperty' "apt-get" ps noninteractiveEnv
|
||||||
|
|
||||||
noninteractiveEnv :: [(String, String)]
|
noninteractiveEnv :: [(String, String)]
|
||||||
|
@ -115,26 +117,26 @@ noninteractiveEnv =
|
||||||
, ("APT_LISTCHANGES_FRONTEND", "none")
|
, ("APT_LISTCHANGES_FRONTEND", "none")
|
||||||
]
|
]
|
||||||
|
|
||||||
update :: Property
|
update :: Property NoInfo
|
||||||
update = runApt ["update"]
|
update = runApt ["update"]
|
||||||
`describe` "apt update"
|
`describe` "apt update"
|
||||||
|
|
||||||
upgrade :: Property
|
upgrade :: Property NoInfo
|
||||||
upgrade = runApt ["-y", "dist-upgrade"]
|
upgrade = runApt ["-y", "dist-upgrade"]
|
||||||
`describe` "apt dist-upgrade"
|
`describe` "apt dist-upgrade"
|
||||||
|
|
||||||
type Package = String
|
type Package = String
|
||||||
|
|
||||||
installed :: [Package] -> Property
|
installed :: [Package] -> Property NoInfo
|
||||||
installed = installed' ["-y"]
|
installed = installed' ["-y"]
|
||||||
|
|
||||||
installed' :: [String] -> [Package] -> Property
|
installed' :: [String] -> [Package] -> Property NoInfo
|
||||||
installed' params ps = robustly $ check (isInstallable ps) go
|
installed' params ps = robustly $ check (isInstallable ps) go
|
||||||
`describe` (unwords $ "apt installed":ps)
|
`describe` (unwords $ "apt installed":ps)
|
||||||
where
|
where
|
||||||
go = runApt $ params ++ ["install"] ++ ps
|
go = runApt $ params ++ ["install"] ++ ps
|
||||||
|
|
||||||
installedBackport :: [Package] -> Property
|
installedBackport :: [Package] -> Property NoInfo
|
||||||
installedBackport ps = trivial $ withOS desc $ \o -> case o of
|
installedBackport ps = trivial $ withOS desc $ \o -> case o of
|
||||||
Nothing -> error "cannot install backports; os not declared"
|
Nothing -> error "cannot install backports; os not declared"
|
||||||
(Just (System (Debian suite) _)) -> case backportSuite suite of
|
(Just (System (Debian suite) _)) -> case backportSuite suite of
|
||||||
|
@ -147,16 +149,16 @@ installedBackport ps = trivial $ withOS desc $ \o -> case o of
|
||||||
notsupported o = error $ "backports not supported on " ++ show o
|
notsupported o = error $ "backports not supported on " ++ show o
|
||||||
|
|
||||||
-- | Minimal install of package, without recommends.
|
-- | Minimal install of package, without recommends.
|
||||||
installedMin :: [Package] -> Property
|
installedMin :: [Package] -> Property NoInfo
|
||||||
installedMin = installed' ["--no-install-recommends", "-y"]
|
installedMin = installed' ["--no-install-recommends", "-y"]
|
||||||
|
|
||||||
removed :: [Package] -> Property
|
removed :: [Package] -> Property NoInfo
|
||||||
removed ps = check (or <$> isInstalled' ps) go
|
removed ps = check (or <$> isInstalled' ps) go
|
||||||
`describe` (unwords $ "apt removed":ps)
|
`describe` (unwords $ "apt removed":ps)
|
||||||
where
|
where
|
||||||
go = runApt $ ["-y", "remove"] ++ ps
|
go = runApt $ ["-y", "remove"] ++ ps
|
||||||
|
|
||||||
buildDep :: [Package] -> Property
|
buildDep :: [Package] -> Property NoInfo
|
||||||
buildDep ps = robustly go
|
buildDep ps = robustly go
|
||||||
`describe` (unwords $ "apt build-dep":ps)
|
`describe` (unwords $ "apt build-dep":ps)
|
||||||
where
|
where
|
||||||
|
@ -165,7 +167,7 @@ buildDep ps = robustly go
|
||||||
-- | Installs the build deps for the source package unpacked
|
-- | Installs the build deps for the source package unpacked
|
||||||
-- in the specifed directory, with a dummy package also
|
-- in the specifed directory, with a dummy package also
|
||||||
-- installed so that autoRemove won't remove them.
|
-- installed so that autoRemove won't remove them.
|
||||||
buildDepIn :: FilePath -> Property
|
buildDepIn :: FilePath -> Property NoInfo
|
||||||
buildDepIn dir = go `requires` installedMin ["devscripts", "equivs"]
|
buildDepIn dir = go `requires` installedMin ["devscripts", "equivs"]
|
||||||
where
|
where
|
||||||
go = cmdProperty' "sh" ["-c", "cd '" ++ dir ++ "' && mk-build-deps debian/control --install --tool 'apt-get -y --no-install-recommends' --remove"]
|
go = cmdProperty' "sh" ["-c", "cd '" ++ dir ++ "' && mk-build-deps debian/control --install --tool 'apt-get -y --no-install-recommends' --remove"]
|
||||||
|
@ -173,11 +175,13 @@ buildDepIn dir = go `requires` installedMin ["devscripts", "equivs"]
|
||||||
|
|
||||||
-- | Package installation may fail becuse the archive has changed.
|
-- | Package installation may fail becuse the archive has changed.
|
||||||
-- Run an update in that case and retry.
|
-- Run an update in that case and retry.
|
||||||
robustly :: Property -> Property
|
robustly :: (Combines (Property i) (Property NoInfo)) => Property i -> Property i
|
||||||
robustly p = adjustProperty p $ \satisfy -> do
|
robustly p = adjustPropertySatisfy p $ \satisfy -> do
|
||||||
r <- satisfy
|
r <- satisfy
|
||||||
if r == FailedChange
|
if r == FailedChange
|
||||||
then ensureProperty $ p `requires` update
|
-- Safe to use ignoreInfo because we're re-running
|
||||||
|
-- the same property.
|
||||||
|
then ensureProperty $ ignoreInfo $ p `requires` update
|
||||||
else return r
|
else return r
|
||||||
|
|
||||||
isInstallable :: [Package] -> IO Bool
|
isInstallable :: [Package] -> IO Bool
|
||||||
|
@ -203,13 +207,13 @@ isInstalled' ps = catMaybes . map parse . lines <$> policy
|
||||||
environ <- addEntry "LANG" "C" <$> getEnvironment
|
environ <- addEntry "LANG" "C" <$> getEnvironment
|
||||||
readProcessEnv "apt-cache" ("policy":ps) (Just environ)
|
readProcessEnv "apt-cache" ("policy":ps) (Just environ)
|
||||||
|
|
||||||
autoRemove :: Property
|
autoRemove :: Property NoInfo
|
||||||
autoRemove = runApt ["-y", "autoremove"]
|
autoRemove = runApt ["-y", "autoremove"]
|
||||||
`describe` "apt autoremove"
|
`describe` "apt autoremove"
|
||||||
|
|
||||||
-- | Enables unattended upgrades. Revert to disable.
|
-- | Enables unattended upgrades. Revert to disable.
|
||||||
unattendedUpgrades :: RevertableProperty
|
unattendedUpgrades :: RevertableProperty
|
||||||
unattendedUpgrades = RevertableProperty enable disable
|
unattendedUpgrades = enable <!> disable
|
||||||
where
|
where
|
||||||
enable = setup True
|
enable = setup True
|
||||||
`before` Service.running "cron"
|
`before` Service.running "cron"
|
||||||
|
@ -237,7 +241,7 @@ unattendedUpgrades = RevertableProperty enable disable
|
||||||
|
|
||||||
-- | Preseeds debconf values and reconfigures the package so it takes
|
-- | Preseeds debconf values and reconfigures the package so it takes
|
||||||
-- effect.
|
-- effect.
|
||||||
reConfigure :: Package -> [(String, String, String)] -> Property
|
reConfigure :: Package -> [(String, String, String)] -> Property NoInfo
|
||||||
reConfigure package vals = reconfigure `requires` setselections
|
reConfigure package vals = reconfigure `requires` setselections
|
||||||
`describe` ("reconfigure " ++ package)
|
`describe` ("reconfigure " ++ package)
|
||||||
where
|
where
|
||||||
|
@ -253,7 +257,7 @@ reConfigure package vals = reconfigure `requires` setselections
|
||||||
--
|
--
|
||||||
-- Assumes that there is a 1:1 mapping between service names and apt
|
-- Assumes that there is a 1:1 mapping between service names and apt
|
||||||
-- package names.
|
-- package names.
|
||||||
serviceInstalledRunning :: Package -> Property
|
serviceInstalledRunning :: Package -> Property NoInfo
|
||||||
serviceInstalledRunning svc = Service.running svc `requires` installed [svc]
|
serviceInstalledRunning svc = Service.running svc `requires` installed [svc]
|
||||||
|
|
||||||
data AptKey = AptKey
|
data AptKey = AptKey
|
||||||
|
@ -262,20 +266,27 @@ data AptKey = AptKey
|
||||||
}
|
}
|
||||||
|
|
||||||
trustsKey :: AptKey -> RevertableProperty
|
trustsKey :: AptKey -> RevertableProperty
|
||||||
trustsKey k = RevertableProperty trust untrust
|
trustsKey k = trustsKey' k <!> untrustKey k
|
||||||
|
|
||||||
|
trustsKey' :: AptKey -> Property NoInfo
|
||||||
|
trustsKey' k = check (not <$> doesFileExist f) $ property desc $ makeChange $ do
|
||||||
|
withHandle StdinHandle createProcessSuccess
|
||||||
|
(proc "gpg" ["--no-default-keyring", "--keyring", f, "--import", "-"]) $ \h -> do
|
||||||
|
hPutStr h (pubkey k)
|
||||||
|
hClose h
|
||||||
|
nukeFile $ f ++ "~" -- gpg dropping
|
||||||
where
|
where
|
||||||
desc = "apt trusts key " ++ keyname k
|
desc = "apt trusts key " ++ keyname k
|
||||||
f = "/etc/apt/trusted.gpg.d" </> keyname k ++ ".gpg"
|
f = aptKeyFile k
|
||||||
untrust = File.notPresent f
|
|
||||||
trust = check (not <$> doesFileExist f) $ property desc $ makeChange $ do
|
untrustKey :: AptKey -> Property NoInfo
|
||||||
withHandle StdinHandle createProcessSuccess
|
untrustKey = File.notPresent . aptKeyFile
|
||||||
(proc "gpg" ["--no-default-keyring", "--keyring", f, "--import", "-"]) $ \h -> do
|
|
||||||
hPutStr h (pubkey k)
|
aptKeyFile :: AptKey -> FilePath
|
||||||
hClose h
|
aptKeyFile k = "/etc/apt/trusted.gpg.d" </> keyname k ++ ".gpg"
|
||||||
nukeFile $ f ++ "~" -- gpg dropping
|
|
||||||
|
|
||||||
-- | Cleans apt's cache of downloaded packages to avoid using up disk
|
-- | Cleans apt's cache of downloaded packages to avoid using up disk
|
||||||
-- space.
|
-- space.
|
||||||
cacheCleaned :: Property
|
cacheCleaned :: Property NoInfo
|
||||||
cacheCleaned = trivial $ cmdProperty "apt-get" ["clean"]
|
cacheCleaned = trivial $ cmdProperty "apt-get" ["clean"]
|
||||||
`describe` "apt cache cleaned"
|
`describe` "apt cache cleaned"
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
|
||||||
module Propellor.Property.Chroot (
|
module Propellor.Property.Chroot (
|
||||||
Chroot(..),
|
Chroot(..),
|
||||||
|
BuilderConf(..),
|
||||||
debootstrapped,
|
debootstrapped,
|
||||||
provisioned,
|
provisioned,
|
||||||
-- * Internal use
|
-- * Internal use
|
||||||
|
@ -10,6 +13,7 @@ module Propellor.Property.Chroot (
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Propellor
|
import Propellor
|
||||||
|
import Propellor.Types.CmdLine
|
||||||
import Propellor.Types.Chroot
|
import Propellor.Types.Chroot
|
||||||
import Propellor.Property.Chroot.Util
|
import Propellor.Property.Chroot.Util
|
||||||
import qualified Propellor.Property.Debootstrap as Debootstrap
|
import qualified Propellor.Property.Debootstrap as Debootstrap
|
||||||
|
@ -28,10 +32,10 @@ data BuilderConf
|
||||||
= UsingDeboostrap Debootstrap.DebootstrapConfig
|
= UsingDeboostrap Debootstrap.DebootstrapConfig
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
instance Hostlike Chroot where
|
instance PropAccum Chroot where
|
||||||
(Chroot l s c h) & p = Chroot l s c (h & p)
|
(Chroot l s c h) & p = Chroot l s c (h & p)
|
||||||
(Chroot l s c h) &^ p = Chroot l s c (h &^ p)
|
(Chroot l s c h) &^ p = Chroot l s c (h &^ p)
|
||||||
getHost (Chroot _ _ _ h) = h
|
getProperties (Chroot _ _ _ h) = hostProperties h
|
||||||
|
|
||||||
-- | Defines a Chroot at the given location, built with debootstrap.
|
-- | Defines a Chroot at the given location, built with debootstrap.
|
||||||
--
|
--
|
||||||
|
@ -57,12 +61,13 @@ debootstrapped system conf location = case system of
|
||||||
provisioned :: Chroot -> RevertableProperty
|
provisioned :: Chroot -> RevertableProperty
|
||||||
provisioned c = provisioned' (propigateChrootInfo c) c False
|
provisioned c = provisioned' (propigateChrootInfo c) c False
|
||||||
|
|
||||||
provisioned' :: (Property -> Property) -> Chroot -> Bool -> RevertableProperty
|
provisioned' :: (Property HasInfo -> Property HasInfo) -> Chroot -> Bool -> RevertableProperty
|
||||||
provisioned' propigator c@(Chroot loc system builderconf _) systemdonly = RevertableProperty
|
provisioned' propigator c@(Chroot loc system builderconf _) systemdonly =
|
||||||
(propigator $ go "exists" setup)
|
(propigator $ go "exists" setup)
|
||||||
|
<!>
|
||||||
(go "removed" teardown)
|
(go "removed" teardown)
|
||||||
where
|
where
|
||||||
go desc a = property (chrootDesc c desc) $ ensureProperties [a]
|
go desc a = propertyList (chrootDesc c desc) [a]
|
||||||
|
|
||||||
setup = propellChroot c (inChrootProcess c) systemdonly
|
setup = propellChroot c (inChrootProcess c) systemdonly
|
||||||
`requires` toProp built
|
`requires` toProp built
|
||||||
|
@ -75,15 +80,21 @@ provisioned' propigator c@(Chroot loc system builderconf _) systemdonly = Revert
|
||||||
|
|
||||||
teardown = toProp (revert built)
|
teardown = toProp (revert built)
|
||||||
|
|
||||||
propigateChrootInfo :: Chroot -> Property -> Property
|
propigateChrootInfo :: (IsProp (Property i)) => Chroot -> Property i -> Property HasInfo
|
||||||
propigateChrootInfo c p = propigateInfo c p (<> chrootInfo c)
|
propigateChrootInfo c p = propigateContainer c p'
|
||||||
|
where
|
||||||
|
p' = infoProperty
|
||||||
|
(propertyDesc p)
|
||||||
|
(propertySatisfy p)
|
||||||
|
(propertyInfo p <> chrootInfo c)
|
||||||
|
(propertyChildren p)
|
||||||
|
|
||||||
chrootInfo :: Chroot -> Info
|
chrootInfo :: Chroot -> Info
|
||||||
chrootInfo (Chroot loc _ _ h) =
|
chrootInfo (Chroot loc _ _ h) =
|
||||||
mempty { _chrootinfo = mempty { _chroots = M.singleton loc h } }
|
mempty { _chrootinfo = mempty { _chroots = M.singleton loc h } }
|
||||||
|
|
||||||
-- | Propellor is run inside the chroot to provision it.
|
-- | Propellor is run inside the chroot to provision it.
|
||||||
propellChroot :: Chroot -> ([String] -> CreateProcess) -> Bool -> Property
|
propellChroot :: Chroot -> ([String] -> CreateProcess) -> Bool -> Property NoInfo
|
||||||
propellChroot c@(Chroot loc _ _ _) mkproc systemdonly = property (chrootDesc c "provisioned") $ do
|
propellChroot c@(Chroot loc _ _ _) mkproc systemdonly = property (chrootDesc c "provisioned") $ do
|
||||||
let d = localdir </> shimdir c
|
let d = localdir </> shimdir c
|
||||||
let me = localdir </> "propellor"
|
let me = localdir </> "propellor"
|
||||||
|
@ -140,7 +151,8 @@ chain hostlist (ChrootChain hn loc systemdonly onconsole) =
|
||||||
r <- runPropellor h $ ensureProperties $
|
r <- runPropellor h $ ensureProperties $
|
||||||
if systemdonly
|
if systemdonly
|
||||||
then [Systemd.installed]
|
then [Systemd.installed]
|
||||||
else hostProperties h
|
else map ignoreInfo $
|
||||||
|
hostProperties h
|
||||||
putStrLn $ "\n" ++ show r
|
putStrLn $ "\n" ++ show r
|
||||||
chain _ _ = errorMessage "bad chain command"
|
chain _ _ = errorMessage "bad chain command"
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,12 @@ import Utility.Env
|
||||||
-- | A property that can be satisfied by running a command.
|
-- | A property that can be satisfied by running a command.
|
||||||
--
|
--
|
||||||
-- The command must exit 0 on success.
|
-- The command must exit 0 on success.
|
||||||
cmdProperty :: String -> [String] -> Property
|
cmdProperty :: String -> [String] -> Property NoInfo
|
||||||
cmdProperty cmd params = cmdProperty' cmd params []
|
cmdProperty cmd params = cmdProperty' cmd params []
|
||||||
|
|
||||||
-- | A property that can be satisfied by running a command,
|
-- | A property that can be satisfied by running a command,
|
||||||
-- with added environment.
|
-- with added environment.
|
||||||
cmdProperty' :: String -> [String] -> [(String, String)] -> Property
|
cmdProperty' :: String -> [String] -> [(String, String)] -> Property NoInfo
|
||||||
cmdProperty' cmd params env = property desc $ liftIO $ do
|
cmdProperty' cmd params env = property desc $ liftIO $ do
|
||||||
env' <- addEntries env <$> getEnvironment
|
env' <- addEntries env <$> getEnvironment
|
||||||
toResult <$> boolSystemEnv cmd (map Param params) (Just env')
|
toResult <$> boolSystemEnv cmd (map Param params) (Just env')
|
||||||
|
@ -32,14 +32,14 @@ cmdProperty' cmd params env = property desc $ liftIO $ do
|
||||||
desc = unwords $ cmd : params
|
desc = unwords $ cmd : params
|
||||||
|
|
||||||
-- | A property that can be satisfied by running a series of shell commands.
|
-- | A property that can be satisfied by running a series of shell commands.
|
||||||
scriptProperty :: [String] -> Property
|
scriptProperty :: [String] -> Property NoInfo
|
||||||
scriptProperty script = cmdProperty "sh" ["-c", shellcmd]
|
scriptProperty script = cmdProperty "sh" ["-c", shellcmd]
|
||||||
where
|
where
|
||||||
shellcmd = intercalate " ; " ("set -e" : script)
|
shellcmd = intercalate " ; " ("set -e" : script)
|
||||||
|
|
||||||
-- | A property that can satisfied by running a series of shell commands,
|
-- | A property that can satisfied by running a series of shell commands,
|
||||||
-- as user (cd'd to their home directory).
|
-- as user (cd'd to their home directory).
|
||||||
userScriptProperty :: UserName -> [String] -> Property
|
userScriptProperty :: UserName -> [String] -> Property NoInfo
|
||||||
userScriptProperty user script = cmdProperty "su" ["-c", shellcmd, user]
|
userScriptProperty user script = cmdProperty "su" ["-c", shellcmd, user]
|
||||||
where
|
where
|
||||||
shellcmd = intercalate " ; " ("set -e" : "cd" : script)
|
shellcmd = intercalate " ; " ("set -e" : "cd" : script)
|
||||||
|
|
|
@ -19,7 +19,7 @@ type CronTimes = String
|
||||||
-- job file.
|
-- job file.
|
||||||
--
|
--
|
||||||
-- The cron job's output will only be emailed if it exits nonzero.
|
-- The cron job's output will only be emailed if it exits nonzero.
|
||||||
job :: Desc -> CronTimes -> UserName -> FilePath -> String -> Property
|
job :: Desc -> CronTimes -> UserName -> FilePath -> String -> Property NoInfo
|
||||||
job desc times user cddir command = combineProperties ("cronned " ++ desc)
|
job desc times user cddir command = combineProperties ("cronned " ++ desc)
|
||||||
[ cronjobfile `File.hasContent`
|
[ cronjobfile `File.hasContent`
|
||||||
[ "# Generated by propellor"
|
[ "# Generated by propellor"
|
||||||
|
@ -52,10 +52,10 @@ job desc times user cddir command = combineProperties ("cronned " ++ desc)
|
||||||
| otherwise = '_'
|
| otherwise = '_'
|
||||||
|
|
||||||
-- | Installs a cron job, and runs it niced and ioniced.
|
-- | Installs a cron job, and runs it niced and ioniced.
|
||||||
niceJob :: Desc -> CronTimes -> UserName -> FilePath -> String -> Property
|
niceJob :: Desc -> CronTimes -> UserName -> FilePath -> String -> Property NoInfo
|
||||||
niceJob desc times user cddir command = job desc times user cddir
|
niceJob desc times user cddir command = job desc times user cddir
|
||||||
("nice ionice -c 3 sh -c " ++ shellEscape command)
|
("nice ionice -c 3 sh -c " ++ shellEscape command)
|
||||||
|
|
||||||
-- | Installs a cron job to run propellor.
|
-- | Installs a cron job to run propellor.
|
||||||
runPropellor :: CronTimes -> Property
|
runPropellor :: CronTimes -> Property NoInfo
|
||||||
runPropellor times = niceJob "propellor" times "root" localdir "./propellor"
|
runPropellor times = niceJob "propellor" times "root" localdir "./propellor"
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
|
||||||
module Propellor.Property.Debootstrap (
|
module Propellor.Property.Debootstrap (
|
||||||
Url,
|
Url,
|
||||||
DebootstrapConfig(..),
|
DebootstrapConfig(..),
|
||||||
|
@ -56,19 +58,18 @@ toParams (c1 :+ c2) = toParams c1 <> toParams c2
|
||||||
-- Note that reverting this property does not stop any processes
|
-- Note that reverting this property does not stop any processes
|
||||||
-- currently running in the chroot.
|
-- currently running in the chroot.
|
||||||
built :: FilePath -> System -> DebootstrapConfig -> RevertableProperty
|
built :: FilePath -> System -> DebootstrapConfig -> RevertableProperty
|
||||||
built = built' (toProp installed)
|
built target system config = built' (toProp installed) target system config <!> teardown
|
||||||
|
|
||||||
built' :: Property -> FilePath -> System -> DebootstrapConfig -> RevertableProperty
|
|
||||||
built' installprop target system@(System _ arch) config =
|
|
||||||
RevertableProperty setup teardown
|
|
||||||
where
|
where
|
||||||
setup = check (unpopulated target <||> ispartial) setupprop
|
|
||||||
`requires` installprop
|
|
||||||
|
|
||||||
teardown = check (not <$> unpopulated target) teardownprop
|
teardown = check (not <$> unpopulated target) teardownprop
|
||||||
|
|
||||||
unpopulated d = null <$> catchDefaultIO [] (dirContents d)
|
teardownprop = property ("removed debootstrapped " ++ target) $
|
||||||
|
makeChange (removetarget target)
|
||||||
|
|
||||||
|
built' :: (Combines (Property NoInfo) (Property i)) => Property i -> FilePath -> System -> DebootstrapConfig -> Property (CInfo NoInfo i)
|
||||||
|
built' installprop target system@(System _ arch) config =
|
||||||
|
check (unpopulated target <||> ispartial) setupprop
|
||||||
|
`requires` installprop
|
||||||
|
where
|
||||||
setupprop = property ("debootstrapped " ++ target) $ liftIO $ do
|
setupprop = property ("debootstrapped " ++ target) $ liftIO $ do
|
||||||
createDirectoryIfMissing True target
|
createDirectoryIfMissing True target
|
||||||
-- Don't allow non-root users to see inside the chroot,
|
-- Don't allow non-root users to see inside the chroot,
|
||||||
|
@ -93,24 +94,25 @@ built' installprop target system@(System _ arch) config =
|
||||||
, return FailedChange
|
, return FailedChange
|
||||||
)
|
)
|
||||||
|
|
||||||
teardownprop = property ("removed debootstrapped " ++ target) $
|
|
||||||
makeChange removetarget
|
|
||||||
|
|
||||||
removetarget = do
|
|
||||||
submnts <- filter (\p -> simplifyPath p /= simplifyPath target)
|
|
||||||
. filter (dirContains target)
|
|
||||||
<$> mountPoints
|
|
||||||
forM_ submnts umountLazy
|
|
||||||
removeDirectoryRecursive target
|
|
||||||
|
|
||||||
-- A failed debootstrap run will leave a debootstrap directory;
|
-- A failed debootstrap run will leave a debootstrap directory;
|
||||||
-- recover by deleting it and trying again.
|
-- recover by deleting it and trying again.
|
||||||
ispartial = ifM (doesDirectoryExist (target </> "debootstrap"))
|
ispartial = ifM (doesDirectoryExist (target </> "debootstrap"))
|
||||||
( do
|
( do
|
||||||
removetarget
|
removetarget target
|
||||||
return True
|
return True
|
||||||
, return False
|
, return False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
unpopulated :: FilePath -> IO Bool
|
||||||
|
unpopulated d = null <$> catchDefaultIO [] (dirContents d)
|
||||||
|
|
||||||
|
removetarget :: FilePath -> IO ()
|
||||||
|
removetarget target = do
|
||||||
|
submnts <- filter (\p -> simplifyPath p /= simplifyPath target)
|
||||||
|
. filter (dirContains target)
|
||||||
|
<$> mountPoints
|
||||||
|
forM_ submnts umountLazy
|
||||||
|
removeDirectoryRecursive target
|
||||||
|
|
||||||
extractSuite :: System -> Maybe String
|
extractSuite :: System -> Maybe String
|
||||||
extractSuite (System (Debian s) _) = Just $ Apt.showSuite s
|
extractSuite (System (Debian s) _) = Just $ Apt.showSuite s
|
||||||
|
@ -122,7 +124,7 @@ extractSuite (System (Ubuntu r) _) = Just r
|
||||||
-- Note that installation from source is done by downloading the tarball
|
-- Note that installation from source is done by downloading the tarball
|
||||||
-- from a Debian mirror, with no cryptographic verification.
|
-- from a Debian mirror, with no cryptographic verification.
|
||||||
installed :: RevertableProperty
|
installed :: RevertableProperty
|
||||||
installed = RevertableProperty install remove
|
installed = install <!> remove
|
||||||
where
|
where
|
||||||
install = withOS "debootstrap installed" $ \o ->
|
install = withOS "debootstrap installed" $ \o ->
|
||||||
ifM (liftIO $ isJust <$> programPath)
|
ifM (liftIO $ isJust <$> programPath)
|
||||||
|
@ -142,18 +144,18 @@ installed = RevertableProperty install remove
|
||||||
aptinstall = Apt.installed ["debootstrap"]
|
aptinstall = Apt.installed ["debootstrap"]
|
||||||
aptremove = Apt.removed ["debootstrap"]
|
aptremove = Apt.removed ["debootstrap"]
|
||||||
|
|
||||||
sourceInstall :: Property
|
sourceInstall :: Property NoInfo
|
||||||
sourceInstall = property "debootstrap installed from source" (liftIO sourceInstall')
|
sourceInstall = property "debootstrap installed from source" (liftIO sourceInstall')
|
||||||
`requires` perlInstalled
|
`requires` perlInstalled
|
||||||
`requires` arInstalled
|
`requires` arInstalled
|
||||||
|
|
||||||
perlInstalled :: Property
|
perlInstalled :: Property NoInfo
|
||||||
perlInstalled = check (not <$> inPath "perl") $ property "perl installed" $
|
perlInstalled = check (not <$> inPath "perl") $ property "perl installed" $
|
||||||
liftIO $ toResult . isJust <$> firstM id
|
liftIO $ toResult . isJust <$> firstM id
|
||||||
[ yumInstall "perl"
|
[ yumInstall "perl"
|
||||||
]
|
]
|
||||||
|
|
||||||
arInstalled :: Property
|
arInstalled :: Property NoInfo
|
||||||
arInstalled = check (not <$> inPath "ar") $ property "ar installed" $
|
arInstalled = check (not <$> inPath "ar") $ property "ar installed" $
|
||||||
liftIO $ toResult . isJust <$> firstM id
|
liftIO $ toResult . isJust <$> firstM id
|
||||||
[ yumInstall "binutils"
|
[ yumInstall "binutils"
|
||||||
|
@ -197,7 +199,7 @@ sourceInstall' = withTmpDir "debootstrap" $ \tmpd -> do
|
||||||
return MadeChange
|
return MadeChange
|
||||||
_ -> errorMessage "debootstrap tar file did not contain exactly one dirctory"
|
_ -> errorMessage "debootstrap tar file did not contain exactly one dirctory"
|
||||||
|
|
||||||
sourceRemove :: Property
|
sourceRemove :: Property NoInfo
|
||||||
sourceRemove = property "debootstrap not installed from source" $ liftIO $
|
sourceRemove = property "debootstrap not installed from source" $ liftIO $
|
||||||
ifM (doesDirectoryExist sourceInstallDir)
|
ifM (doesDirectoryExist sourceInstallDir)
|
||||||
( do
|
( do
|
||||||
|
|
|
@ -58,7 +58,7 @@ import Data.List
|
||||||
-- In either case, the secondary dns server Host should have an ipv4 and/or
|
-- In either case, the secondary dns server Host should have an ipv4 and/or
|
||||||
-- ipv6 property defined.
|
-- ipv6 property defined.
|
||||||
primary :: [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty
|
primary :: [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty
|
||||||
primary hosts domain soa rs = RevertableProperty setup cleanup
|
primary hosts domain soa rs = setup <!> cleanup
|
||||||
where
|
where
|
||||||
setup = setupPrimary zonefile id hosts domain soa rs
|
setup = setupPrimary zonefile id hosts domain soa rs
|
||||||
`onChange` Service.reloaded "bind9"
|
`onChange` Service.reloaded "bind9"
|
||||||
|
@ -67,7 +67,7 @@ primary hosts domain soa rs = RevertableProperty setup cleanup
|
||||||
|
|
||||||
zonefile = "/etc/bind/propellor/db." ++ domain
|
zonefile = "/etc/bind/propellor/db." ++ domain
|
||||||
|
|
||||||
setupPrimary :: FilePath -> (FilePath -> FilePath) -> [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> Property
|
setupPrimary :: FilePath -> (FilePath -> FilePath) -> [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> Property HasInfo
|
||||||
setupPrimary zonefile mknamedconffile hosts domain soa rs =
|
setupPrimary zonefile mknamedconffile hosts domain soa rs =
|
||||||
withwarnings baseprop
|
withwarnings baseprop
|
||||||
`requires` servingZones
|
`requires` servingZones
|
||||||
|
@ -77,8 +77,8 @@ setupPrimary zonefile mknamedconffile hosts domain soa rs =
|
||||||
indomain = M.elems $ M.filterWithKey (\hn _ -> inDomain domain $ AbsDomain $ hn) hostmap
|
indomain = M.elems $ M.filterWithKey (\hn _ -> inDomain domain $ AbsDomain $ hn) hostmap
|
||||||
|
|
||||||
(partialzone, zonewarnings) = genZone indomain hostmap domain soa
|
(partialzone, zonewarnings) = genZone indomain hostmap domain soa
|
||||||
baseprop = Property ("dns primary for " ++ domain) satisfy
|
baseprop = infoProperty ("dns primary for " ++ domain) satisfy
|
||||||
(addNamedConf conf)
|
(addNamedConf conf) []
|
||||||
satisfy = do
|
satisfy = do
|
||||||
sshfps <- concat <$> mapM (genSSHFP domain) (M.elems hostmap)
|
sshfps <- concat <$> mapM (genSSHFP domain) (M.elems hostmap)
|
||||||
let zone = partialzone
|
let zone = partialzone
|
||||||
|
@ -87,7 +87,7 @@ setupPrimary zonefile mknamedconffile hosts domain soa rs =
|
||||||
( makeChange $ writeZoneFile zone zonefile
|
( makeChange $ writeZoneFile zone zonefile
|
||||||
, noChange
|
, noChange
|
||||||
)
|
)
|
||||||
withwarnings p = adjustProperty p $ \a -> do
|
withwarnings p = adjustPropertySatisfy p $ \a -> do
|
||||||
mapM_ warningMessage $ zonewarnings ++ secondarywarnings
|
mapM_ warningMessage $ zonewarnings ++ secondarywarnings
|
||||||
a
|
a
|
||||||
conf = NamedConf
|
conf = NamedConf
|
||||||
|
@ -117,7 +117,7 @@ setupPrimary zonefile mknamedconffile hosts domain soa rs =
|
||||||
in z /= oldzone || oldserial < sSerial (zSOA zone)
|
in z /= oldzone || oldserial < sSerial (zSOA zone)
|
||||||
|
|
||||||
|
|
||||||
cleanupPrimary :: FilePath -> Domain -> Property
|
cleanupPrimary :: FilePath -> Domain -> Property NoInfo
|
||||||
cleanupPrimary zonefile domain = check (doesFileExist zonefile) $
|
cleanupPrimary zonefile domain = check (doesFileExist zonefile) $
|
||||||
property ("removed dns primary for " ++ domain)
|
property ("removed dns primary for " ++ domain)
|
||||||
(makeChange $ removeZoneFile zonefile)
|
(makeChange $ removeZoneFile zonefile)
|
||||||
|
@ -150,13 +150,14 @@ cleanupPrimary zonefile domain = check (doesFileExist zonefile) $
|
||||||
-- want to later disable DNSSEC you will need to adjust the serial number
|
-- want to later disable DNSSEC you will need to adjust the serial number
|
||||||
-- passed to mkSOA to ensure it is larger.
|
-- passed to mkSOA to ensure it is larger.
|
||||||
signedPrimary :: Recurrance -> [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty
|
signedPrimary :: Recurrance -> [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty
|
||||||
signedPrimary recurrance hosts domain soa rs = RevertableProperty setup cleanup
|
signedPrimary recurrance hosts domain soa rs = setup <!> cleanup
|
||||||
where
|
where
|
||||||
setup = combineProperties ("dns primary for " ++ domain ++ " (signed)")
|
setup = combineProperties ("dns primary for " ++ domain ++ " (signed)")
|
||||||
[ setupPrimary zonefile signedZoneFile hosts domain soa rs'
|
(props
|
||||||
, toProp (zoneSigned domain zonefile)
|
& setupPrimary zonefile signedZoneFile hosts domain soa rs'
|
||||||
, forceZoneSigned domain zonefile `period` recurrance
|
& zoneSigned domain zonefile
|
||||||
]
|
& forceZoneSigned domain zonefile `period` recurrance
|
||||||
|
)
|
||||||
`onChange` Service.reloaded "bind9"
|
`onChange` Service.reloaded "bind9"
|
||||||
|
|
||||||
cleanup = cleanupPrimary zonefile domain
|
cleanup = cleanupPrimary zonefile domain
|
||||||
|
@ -186,7 +187,7 @@ secondary hosts domain = secondaryFor (otherServers Master hosts domain) hosts d
|
||||||
-- | This variant is useful if the primary server does not have its DNS
|
-- | This variant is useful if the primary server does not have its DNS
|
||||||
-- configured via propellor.
|
-- configured via propellor.
|
||||||
secondaryFor :: [HostName] -> [Host] -> Domain -> RevertableProperty
|
secondaryFor :: [HostName] -> [Host] -> Domain -> RevertableProperty
|
||||||
secondaryFor masters hosts domain = RevertableProperty setup cleanup
|
secondaryFor masters hosts domain = setup <!> cleanup
|
||||||
where
|
where
|
||||||
setup = pureInfoProperty desc (addNamedConf conf)
|
setup = pureInfoProperty desc (addNamedConf conf)
|
||||||
`requires` servingZones
|
`requires` servingZones
|
||||||
|
@ -214,12 +215,12 @@ otherServers wantedtype hosts domain =
|
||||||
-- | Rewrites the whole named.conf.local file to serve the zones
|
-- | Rewrites the whole named.conf.local file to serve the zones
|
||||||
-- configured by `primary` and `secondary`, and ensures that bind9 is
|
-- configured by `primary` and `secondary`, and ensures that bind9 is
|
||||||
-- running.
|
-- running.
|
||||||
servingZones :: Property
|
servingZones :: Property NoInfo
|
||||||
servingZones = namedConfWritten
|
servingZones = namedConfWritten
|
||||||
`onChange` Service.reloaded "bind9"
|
`onChange` Service.reloaded "bind9"
|
||||||
`requires` Apt.serviceInstalledRunning "bind9"
|
`requires` Apt.serviceInstalledRunning "bind9"
|
||||||
|
|
||||||
namedConfWritten :: Property
|
namedConfWritten :: Property NoInfo
|
||||||
namedConfWritten = property "named.conf configured" $ do
|
namedConfWritten = property "named.conf configured" $ do
|
||||||
zs <- getNamedConf
|
zs <- getNamedConf
|
||||||
ensureProperty $
|
ensureProperty $
|
||||||
|
|
|
@ -8,7 +8,7 @@ import qualified Propellor.Property.File as File
|
||||||
-- signedPrimary uses this, so this property does not normally need to be
|
-- signedPrimary uses this, so this property does not normally need to be
|
||||||
-- used directly.
|
-- used directly.
|
||||||
keysInstalled :: Domain -> RevertableProperty
|
keysInstalled :: Domain -> RevertableProperty
|
||||||
keysInstalled domain = RevertableProperty setup cleanup
|
keysInstalled domain = setup <!> cleanup
|
||||||
where
|
where
|
||||||
setup = propertyList "DNSSEC keys installed" $
|
setup = propertyList "DNSSEC keys installed" $
|
||||||
map installkey keys
|
map installkey keys
|
||||||
|
@ -38,16 +38,14 @@ keysInstalled domain = RevertableProperty setup cleanup
|
||||||
-- signedPrimary uses this, so this property does not normally need to be
|
-- signedPrimary uses this, so this property does not normally need to be
|
||||||
-- used directly.
|
-- used directly.
|
||||||
zoneSigned :: Domain -> FilePath -> RevertableProperty
|
zoneSigned :: Domain -> FilePath -> RevertableProperty
|
||||||
zoneSigned domain zonefile = RevertableProperty setup cleanup
|
zoneSigned domain zonefile = setup <!> cleanup
|
||||||
where
|
where
|
||||||
setup = check needupdate (forceZoneSigned domain zonefile)
|
setup = check needupdate (forceZoneSigned domain zonefile)
|
||||||
`requires` toProp (keysInstalled domain)
|
`requires` toProp (keysInstalled domain)
|
||||||
|
|
||||||
cleanup = combineProperties ("removed signed zone for " ++ domain)
|
cleanup = File.notPresent (signedZoneFile zonefile)
|
||||||
[ File.notPresent (signedZoneFile zonefile)
|
`before` File.notPresent dssetfile
|
||||||
, File.notPresent dssetfile
|
`before` toProp (revert (keysInstalled domain))
|
||||||
, toProp (revert (keysInstalled domain))
|
|
||||||
]
|
|
||||||
|
|
||||||
dssetfile = dir </> "-" ++ domain ++ "."
|
dssetfile = dir </> "-" ++ domain ++ "."
|
||||||
dir = takeDirectory zonefile
|
dir = takeDirectory zonefile
|
||||||
|
@ -65,7 +63,7 @@ zoneSigned domain zonefile = RevertableProperty setup cleanup
|
||||||
t2 <- getModificationTime f
|
t2 <- getModificationTime f
|
||||||
return (t2 >= t1)
|
return (t2 >= t1)
|
||||||
|
|
||||||
forceZoneSigned :: Domain -> FilePath -> Property
|
forceZoneSigned :: Domain -> FilePath -> Property NoInfo
|
||||||
forceZoneSigned domain zonefile = property ("zone signed for " ++ domain) $ liftIO $ do
|
forceZoneSigned domain zonefile = property ("zone signed for " ++ domain) $ liftIO $ do
|
||||||
salt <- take 16 <$> saltSha1
|
salt <- take 16 <$> saltSha1
|
||||||
let p = proc "dnssec-signzone"
|
let p = proc "dnssec-signzone"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{-# LANGUAGE BangPatterns #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
|
||||||
-- | Docker support for propellor
|
-- | Docker support for propellor
|
||||||
--
|
--
|
||||||
|
@ -40,6 +40,7 @@ module Propellor.Property.Docker (
|
||||||
|
|
||||||
import Propellor hiding (init)
|
import Propellor hiding (init)
|
||||||
import Propellor.Types.Docker
|
import Propellor.Types.Docker
|
||||||
|
import Propellor.Types.CmdLine
|
||||||
import qualified Propellor.Property.File as File
|
import qualified Propellor.Property.File as File
|
||||||
import qualified Propellor.Property.Apt as Apt
|
import qualified Propellor.Property.Apt as Apt
|
||||||
import qualified Propellor.Shim as Shim
|
import qualified Propellor.Shim as Shim
|
||||||
|
@ -55,12 +56,12 @@ import Data.List hiding (init)
|
||||||
import Data.List.Utils
|
import Data.List.Utils
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
|
|
||||||
installed :: Property
|
installed :: Property NoInfo
|
||||||
installed = Apt.installed ["docker.io"]
|
installed = Apt.installed ["docker.io"]
|
||||||
|
|
||||||
-- | Configures docker with an authentication file, so that images can be
|
-- | Configures docker with an authentication file, so that images can be
|
||||||
-- pushed to index.docker.io. Optional.
|
-- pushed to index.docker.io. Optional.
|
||||||
configured :: Property
|
configured :: Property HasInfo
|
||||||
configured = prop `requires` installed
|
configured = prop `requires` installed
|
||||||
where
|
where
|
||||||
prop = withPrivData src anyContext $ \getcfg ->
|
prop = withPrivData src anyContext $ \getcfg ->
|
||||||
|
@ -77,10 +78,10 @@ type ContainerName = String
|
||||||
-- | A docker container.
|
-- | A docker container.
|
||||||
data Container = Container Image Host
|
data Container = Container Image Host
|
||||||
|
|
||||||
instance Hostlike Container where
|
instance PropAccum Container where
|
||||||
(Container i h) & p = Container i (h & p)
|
(Container i h) & p = Container i (h & p)
|
||||||
(Container i h) &^ p = Container i (h &^ p)
|
(Container i h) &^ p = Container i (h &^ p)
|
||||||
getHost (Container _ h) = h
|
getProperties (Container _ h) = hostProperties h
|
||||||
|
|
||||||
-- | Defines a Container with a given name, image, and properties.
|
-- | Defines a Container with a given name, image, and properties.
|
||||||
-- Properties can be added to configure the Container.
|
-- Properties can be added to configure the Container.
|
||||||
|
@ -105,8 +106,9 @@ container cn image = Container image (Host cn [] info)
|
||||||
-- Reverting this property ensures that the container is stopped and
|
-- Reverting this property ensures that the container is stopped and
|
||||||
-- removed.
|
-- removed.
|
||||||
docked :: Container -> RevertableProperty
|
docked :: Container -> RevertableProperty
|
||||||
docked ctr@(Container _ h) = RevertableProperty
|
docked ctr@(Container _ h) =
|
||||||
(propigateContainerInfo ctr (go "docked" setup))
|
(propigateContainerInfo ctr (go "docked" setup))
|
||||||
|
<!>
|
||||||
(go "undocked" teardown)
|
(go "undocked" teardown)
|
||||||
where
|
where
|
||||||
cn = hostName h
|
cn = hostName h
|
||||||
|
@ -133,10 +135,14 @@ docked ctr@(Container _ h) = RevertableProperty
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
propigateContainerInfo :: Container -> Property -> Property
|
propigateContainerInfo :: (IsProp (Property i)) => Container -> Property i -> Property HasInfo
|
||||||
propigateContainerInfo ctr@(Container _ h) p =
|
propigateContainerInfo ctr@(Container _ h) p = propigateContainer ctr p'
|
||||||
propigateInfo ctr p (<> dockerinfo)
|
|
||||||
where
|
where
|
||||||
|
p' = infoProperty
|
||||||
|
(propertyDesc p)
|
||||||
|
(propertySatisfy p)
|
||||||
|
(propertyInfo p <> dockerinfo)
|
||||||
|
(propertyChildren p)
|
||||||
dockerinfo = dockerInfo $
|
dockerinfo = dockerInfo $
|
||||||
mempty { _dockerContainers = M.singleton (hostName h) h }
|
mempty { _dockerContainers = M.singleton (hostName h) h }
|
||||||
|
|
||||||
|
@ -164,7 +170,7 @@ mkContainerInfo cid@(ContainerId hn _cn) (Container img h) =
|
||||||
-- that were not set up using propellor.
|
-- that were not set up using propellor.
|
||||||
--
|
--
|
||||||
-- Generally, should come after the properties for the desired containers.
|
-- Generally, should come after the properties for the desired containers.
|
||||||
garbageCollected :: Property
|
garbageCollected :: Property NoInfo
|
||||||
garbageCollected = propertyList "docker garbage collected"
|
garbageCollected = propertyList "docker garbage collected"
|
||||||
[ gccontainers
|
[ gccontainers
|
||||||
, gcimages
|
, gcimages
|
||||||
|
@ -180,7 +186,7 @@ garbageCollected = propertyList "docker garbage collected"
|
||||||
-- Currently, this consists of making pam_loginuid lines optional in
|
-- Currently, this consists of making pam_loginuid lines optional in
|
||||||
-- the pam config, to work around <https://github.com/docker/docker/issues/5663>
|
-- the pam config, to work around <https://github.com/docker/docker/issues/5663>
|
||||||
-- which affects docker 1.2.0.
|
-- which affects docker 1.2.0.
|
||||||
tweaked :: Property
|
tweaked :: Property NoInfo
|
||||||
tweaked = trivial $
|
tweaked = trivial $
|
||||||
cmdProperty "sh" ["-c", "sed -ri 's/^session\\s+required\\s+pam_loginuid.so$/session optional pam_loginuid.so/' /etc/pam.d/*"]
|
cmdProperty "sh" ["-c", "sed -ri 's/^session\\s+required\\s+pam_loginuid.so$/session optional pam_loginuid.so/' /etc/pam.d/*"]
|
||||||
`describe` "tweaked for docker"
|
`describe` "tweaked for docker"
|
||||||
|
@ -191,7 +197,7 @@ tweaked = trivial $
|
||||||
-- other GRUB_CMDLINE_LINUX_DEFAULT settings.
|
-- other GRUB_CMDLINE_LINUX_DEFAULT settings.
|
||||||
--
|
--
|
||||||
-- Only takes effect after reboot. (Not automated.)
|
-- Only takes effect after reboot. (Not automated.)
|
||||||
memoryLimited :: Property
|
memoryLimited :: Property NoInfo
|
||||||
memoryLimited = "/etc/default/grub" `File.containsLine` cfg
|
memoryLimited = "/etc/default/grub" `File.containsLine` cfg
|
||||||
`describe` "docker memory limited"
|
`describe` "docker memory limited"
|
||||||
`onChange` cmdProperty "update-grub" []
|
`onChange` cmdProperty "update-grub" []
|
||||||
|
@ -208,44 +214,44 @@ type RunParam = String
|
||||||
type Image = String
|
type Image = String
|
||||||
|
|
||||||
-- | Set custom dns server for container.
|
-- | Set custom dns server for container.
|
||||||
dns :: String -> Property
|
dns :: String -> Property HasInfo
|
||||||
dns = runProp "dns"
|
dns = runProp "dns"
|
||||||
|
|
||||||
-- | Set container host name.
|
-- | Set container host name.
|
||||||
hostname :: String -> Property
|
hostname :: String -> Property HasInfo
|
||||||
hostname = runProp "hostname"
|
hostname = runProp "hostname"
|
||||||
|
|
||||||
-- | Set name of container.
|
-- | Set name of container.
|
||||||
name :: String -> Property
|
name :: String -> Property HasInfo
|
||||||
name = runProp "name"
|
name = runProp "name"
|
||||||
|
|
||||||
-- | Publish a container's port to the host
|
-- | Publish a container's port to the host
|
||||||
-- (format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort)
|
-- (format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort)
|
||||||
publish :: String -> Property
|
publish :: String -> Property HasInfo
|
||||||
publish = runProp "publish"
|
publish = runProp "publish"
|
||||||
|
|
||||||
-- | Expose a container's port without publishing it.
|
-- | Expose a container's port without publishing it.
|
||||||
expose :: String -> Property
|
expose :: String -> Property HasInfo
|
||||||
expose = runProp "expose"
|
expose = runProp "expose"
|
||||||
|
|
||||||
-- | Username or UID for container.
|
-- | Username or UID for container.
|
||||||
user :: String -> Property
|
user :: String -> Property HasInfo
|
||||||
user = runProp "user"
|
user = runProp "user"
|
||||||
|
|
||||||
-- | Mount a volume
|
-- | Mount a volume
|
||||||
-- Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]
|
-- Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]
|
||||||
-- With just a directory, creates a volume in the container.
|
-- With just a directory, creates a volume in the container.
|
||||||
volume :: String -> Property
|
volume :: String -> Property HasInfo
|
||||||
volume = runProp "volume"
|
volume = runProp "volume"
|
||||||
|
|
||||||
-- | Mount a volume from the specified container into the current
|
-- | Mount a volume from the specified container into the current
|
||||||
-- container.
|
-- container.
|
||||||
volumes_from :: ContainerName -> Property
|
volumes_from :: ContainerName -> Property HasInfo
|
||||||
volumes_from cn = genProp "volumes-from" $ \hn ->
|
volumes_from cn = genProp "volumes-from" $ \hn ->
|
||||||
fromContainerId (ContainerId hn cn)
|
fromContainerId (ContainerId hn cn)
|
||||||
|
|
||||||
-- | Work dir inside the container.
|
-- | Work dir inside the container.
|
||||||
workdir :: String -> Property
|
workdir :: String -> Property HasInfo
|
||||||
workdir = runProp "workdir"
|
workdir = runProp "workdir"
|
||||||
|
|
||||||
-- | Memory limit for container.
|
-- | Memory limit for container.
|
||||||
|
@ -253,18 +259,18 @@ workdir = runProp "workdir"
|
||||||
--
|
--
|
||||||
-- Note: Only takes effect when the host has the memoryLimited property
|
-- Note: Only takes effect when the host has the memoryLimited property
|
||||||
-- enabled.
|
-- enabled.
|
||||||
memory :: String -> Property
|
memory :: String -> Property HasInfo
|
||||||
memory = runProp "memory"
|
memory = runProp "memory"
|
||||||
|
|
||||||
-- | CPU shares (relative weight).
|
-- | CPU shares (relative weight).
|
||||||
--
|
--
|
||||||
-- By default, all containers run at the same priority, but you can tell
|
-- 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.
|
-- the kernel to give more CPU time to a container using this property.
|
||||||
cpuShares :: Int -> Property
|
cpuShares :: Int -> Property HasInfo
|
||||||
cpuShares = runProp "cpu-shares" . show
|
cpuShares = runProp "cpu-shares" . show
|
||||||
|
|
||||||
-- | Link with another container on the same host.
|
-- | Link with another container on the same host.
|
||||||
link :: ContainerName -> ContainerAlias -> Property
|
link :: ContainerName -> ContainerAlias -> Property HasInfo
|
||||||
link linkwith calias = genProp "link" $ \hn ->
|
link linkwith calias = genProp "link" $ \hn ->
|
||||||
fromContainerId (ContainerId hn linkwith) ++ ":" ++ calias
|
fromContainerId (ContainerId hn linkwith) ++ ":" ++ calias
|
||||||
|
|
||||||
|
@ -276,19 +282,19 @@ type ContainerAlias = String
|
||||||
-- propellor; as well as keeping badly behaved containers running,
|
-- propellor; as well as keeping badly behaved containers running,
|
||||||
-- it ensures that containers get started back up after reboot or
|
-- it ensures that containers get started back up after reboot or
|
||||||
-- after docker is upgraded.
|
-- after docker is upgraded.
|
||||||
restartAlways :: Property
|
restartAlways :: Property HasInfo
|
||||||
restartAlways = runProp "restart" "always"
|
restartAlways = runProp "restart" "always"
|
||||||
|
|
||||||
-- | Docker will restart the container if it exits nonzero.
|
-- | Docker will restart the container if it exits nonzero.
|
||||||
-- If a number is provided, it will be restarted only up to that many
|
-- If a number is provided, it will be restarted only up to that many
|
||||||
-- times.
|
-- times.
|
||||||
restartOnFailure :: Maybe Int -> Property
|
restartOnFailure :: Maybe Int -> Property HasInfo
|
||||||
restartOnFailure Nothing = runProp "restart" "on-failure"
|
restartOnFailure Nothing = runProp "restart" "on-failure"
|
||||||
restartOnFailure (Just n) = runProp "restart" ("on-failure:" ++ show n)
|
restartOnFailure (Just n) = runProp "restart" ("on-failure:" ++ show n)
|
||||||
|
|
||||||
-- | Makes docker not restart a container when it exits
|
-- | Makes docker not restart a container when it exits
|
||||||
-- Note that this includes not restarting it on boot!
|
-- Note that this includes not restarting it on boot!
|
||||||
restartNever :: Property
|
restartNever :: Property HasInfo
|
||||||
restartNever = runProp "restart" "no"
|
restartNever = runProp "restart" "no"
|
||||||
|
|
||||||
-- | A container is identified by its name, and the host
|
-- | A container is identified by its name, and the host
|
||||||
|
@ -322,12 +328,12 @@ fromContainerId (ContainerId hn cn) = cn++"."++hn++myContainerSuffix
|
||||||
myContainerSuffix :: String
|
myContainerSuffix :: String
|
||||||
myContainerSuffix = ".propellor"
|
myContainerSuffix = ".propellor"
|
||||||
|
|
||||||
containerDesc :: ContainerId -> Property -> Property
|
containerDesc :: (IsProp (Property i)) => ContainerId -> Property i -> Property i
|
||||||
containerDesc cid p = p `describe` desc
|
containerDesc cid p = p `describe` desc
|
||||||
where
|
where
|
||||||
desc = "container " ++ fromContainerId cid ++ " " ++ propertyDesc p
|
desc = "container " ++ fromContainerId cid ++ " " ++ propertyDesc p
|
||||||
|
|
||||||
runningContainer :: ContainerId -> Image -> [RunParam] -> Property
|
runningContainer :: ContainerId -> Image -> [RunParam] -> Property NoInfo
|
||||||
runningContainer cid@(ContainerId hn cn) image runps = containerDesc cid $ property "running" $ do
|
runningContainer cid@(ContainerId hn cn) image runps = containerDesc cid $ property "running" $ do
|
||||||
l <- liftIO $ listContainers RunningContainers
|
l <- liftIO $ listContainers RunningContainers
|
||||||
if cid `elem` l
|
if cid `elem` l
|
||||||
|
@ -442,7 +448,7 @@ init s = case toContainerId s of
|
||||||
|
|
||||||
-- | Once a container is running, propellor can be run inside
|
-- | Once a container is running, propellor can be run inside
|
||||||
-- it to provision it.
|
-- it to provision it.
|
||||||
provisionContainer :: ContainerId -> Property
|
provisionContainer :: ContainerId -> Property NoInfo
|
||||||
provisionContainer cid = containerDesc cid $ property "provisioned" $ liftIO $ do
|
provisionContainer cid = containerDesc cid $ property "provisioned" $ liftIO $ do
|
||||||
let shim = Shim.file (localdir </> "propellor") (localdir </> shimdir cid)
|
let shim = Shim.file (localdir </> "propellor") (localdir </> shimdir cid)
|
||||||
let params = ["--continue", show $ toChain cid]
|
let params = ["--continue", show $ toChain cid]
|
||||||
|
@ -472,7 +478,8 @@ chain hostlist hn s = case toContainerId s of
|
||||||
changeWorkingDirectory localdir
|
changeWorkingDirectory localdir
|
||||||
onlyProcess (provisioningLock cid) $ do
|
onlyProcess (provisioningLock cid) $ do
|
||||||
r <- runPropellor h $ ensureProperties $
|
r <- runPropellor h $ ensureProperties $
|
||||||
hostProperties h
|
map ignoreInfo $
|
||||||
|
hostProperties h
|
||||||
putStrLn $ "\n" ++ show r
|
putStrLn $ "\n" ++ show r
|
||||||
|
|
||||||
stopContainer :: ContainerId -> IO Bool
|
stopContainer :: ContainerId -> IO Bool
|
||||||
|
@ -481,7 +488,7 @@ stopContainer cid = boolSystem dockercmd [Param "stop", Param $ fromContainerId
|
||||||
startContainer :: ContainerId -> IO Bool
|
startContainer :: ContainerId -> IO Bool
|
||||||
startContainer cid = boolSystem dockercmd [Param "start", Param $ fromContainerId cid ]
|
startContainer cid = boolSystem dockercmd [Param "start", Param $ fromContainerId cid ]
|
||||||
|
|
||||||
stoppedContainer :: ContainerId -> Property
|
stoppedContainer :: ContainerId -> Property NoInfo
|
||||||
stoppedContainer cid = containerDesc cid $ property desc $
|
stoppedContainer cid = containerDesc cid $ property desc $
|
||||||
ifM (liftIO $ elem cid <$> listContainers RunningContainers)
|
ifM (liftIO $ elem cid <$> listContainers RunningContainers)
|
||||||
( liftIO cleanup `after` ensureProperty
|
( liftIO cleanup `after` ensureProperty
|
||||||
|
@ -533,13 +540,13 @@ listContainers status =
|
||||||
listImages :: IO [Image]
|
listImages :: IO [Image]
|
||||||
listImages = lines <$> readProcess dockercmd ["images", "--all", "--quiet"]
|
listImages = lines <$> readProcess dockercmd ["images", "--all", "--quiet"]
|
||||||
|
|
||||||
runProp :: String -> RunParam -> Property
|
runProp :: String -> RunParam -> Property HasInfo
|
||||||
runProp field val = pureInfoProperty (param) $ dockerInfo $
|
runProp field val = pureInfoProperty (param) $ dockerInfo $
|
||||||
mempty { _dockerRunParams = [DockerRunParam (\_ -> "--"++param)] }
|
mempty { _dockerRunParams = [DockerRunParam (\_ -> "--"++param)] }
|
||||||
where
|
where
|
||||||
param = field++"="++val
|
param = field++"="++val
|
||||||
|
|
||||||
genProp :: String -> (HostName -> RunParam) -> Property
|
genProp :: String -> (HostName -> RunParam) -> Property HasInfo
|
||||||
genProp field mkval = pureInfoProperty field $ dockerInfo $
|
genProp field mkval = pureInfoProperty field $ dockerInfo $
|
||||||
mempty { _dockerRunParams = [DockerRunParam (\hn -> "--"++field++"=" ++ mkval hn)] }
|
mempty { _dockerRunParams = [DockerRunParam (\hn -> "--"++field++"=" ++ mkval hn)] }
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import System.PosixCompat.Types
|
||||||
type Line = String
|
type Line = String
|
||||||
|
|
||||||
-- | Replaces all the content of a file.
|
-- | Replaces all the content of a file.
|
||||||
hasContent :: FilePath -> [Line] -> Property
|
hasContent :: FilePath -> [Line] -> Property NoInfo
|
||||||
f `hasContent` newcontent = fileProperty ("replace " ++ f)
|
f `hasContent` newcontent = fileProperty ("replace " ++ f)
|
||||||
(\_oldcontent -> newcontent) f
|
(\_oldcontent -> newcontent) f
|
||||||
|
|
||||||
|
@ -17,25 +17,25 @@ f `hasContent` newcontent = fileProperty ("replace " ++ f)
|
||||||
--
|
--
|
||||||
-- The file's permissions are preserved if the file already existed.
|
-- The file's permissions are preserved if the file already existed.
|
||||||
-- Otherwise, they're set to 600.
|
-- Otherwise, they're set to 600.
|
||||||
hasPrivContent :: IsContext c => FilePath -> c -> Property
|
hasPrivContent :: IsContext c => FilePath -> c -> Property HasInfo
|
||||||
hasPrivContent f = hasPrivContentFrom (PrivDataSourceFile (PrivFile f) f) f
|
hasPrivContent f = hasPrivContentFrom (PrivDataSourceFile (PrivFile f) f) f
|
||||||
|
|
||||||
-- | Like hasPrivContent, but allows specifying a source
|
-- | Like hasPrivContent, but allows specifying a source
|
||||||
-- for PrivData, rather than using PrivDataSourceFile.
|
-- for PrivData, rather than using PrivDataSourceFile.
|
||||||
hasPrivContentFrom :: (IsContext c, IsPrivDataSource s) => s -> FilePath -> c -> Property
|
hasPrivContentFrom :: (IsContext c, IsPrivDataSource s) => s -> FilePath -> c -> Property HasInfo
|
||||||
hasPrivContentFrom = hasPrivContent' writeFileProtected
|
hasPrivContentFrom = hasPrivContent' writeFileProtected
|
||||||
|
|
||||||
-- | Leaves the file at its default or current mode,
|
-- | Leaves the file at its default or current mode,
|
||||||
-- allowing "private" data to be read.
|
-- allowing "private" data to be read.
|
||||||
--
|
--
|
||||||
-- Use with caution!
|
-- Use with caution!
|
||||||
hasPrivContentExposed :: IsContext c => FilePath -> c -> Property
|
hasPrivContentExposed :: IsContext c => FilePath -> c -> Property HasInfo
|
||||||
hasPrivContentExposed f = hasPrivContentExposedFrom (PrivDataSourceFile (PrivFile f) f) f
|
hasPrivContentExposed f = hasPrivContentExposedFrom (PrivDataSourceFile (PrivFile f) f) f
|
||||||
|
|
||||||
hasPrivContentExposedFrom :: (IsContext c, IsPrivDataSource s) => s -> FilePath -> c -> Property
|
hasPrivContentExposedFrom :: (IsContext c, IsPrivDataSource s) => s -> FilePath -> c -> Property HasInfo
|
||||||
hasPrivContentExposedFrom = hasPrivContent' writeFile
|
hasPrivContentExposedFrom = hasPrivContent' writeFile
|
||||||
|
|
||||||
hasPrivContent' :: (IsContext c, IsPrivDataSource s) => (String -> FilePath -> IO ()) -> s -> FilePath -> c -> Property
|
hasPrivContent' :: (IsContext c, IsPrivDataSource s) => (String -> FilePath -> IO ()) -> s -> FilePath -> c -> Property HasInfo
|
||||||
hasPrivContent' writer source f context =
|
hasPrivContent' writer source f context =
|
||||||
withPrivData source context $ \getcontent ->
|
withPrivData source context $ \getcontent ->
|
||||||
property desc $ getcontent $ \privcontent ->
|
property desc $ getcontent $ \privcontent ->
|
||||||
|
@ -45,10 +45,10 @@ hasPrivContent' writer source f context =
|
||||||
desc = "privcontent " ++ f
|
desc = "privcontent " ++ f
|
||||||
|
|
||||||
-- | Ensures that a line is present in a file, adding it to the end if not.
|
-- | Ensures that a line is present in a file, adding it to the end if not.
|
||||||
containsLine :: FilePath -> Line -> Property
|
containsLine :: FilePath -> Line -> Property NoInfo
|
||||||
f `containsLine` l = f `containsLines` [l]
|
f `containsLine` l = f `containsLines` [l]
|
||||||
|
|
||||||
containsLines :: FilePath -> [Line] -> Property
|
containsLines :: FilePath -> [Line] -> Property NoInfo
|
||||||
f `containsLines` ls = fileProperty (f ++ " contains:" ++ show ls) go f
|
f `containsLines` ls = fileProperty (f ++ " contains:" ++ show ls) go f
|
||||||
where
|
where
|
||||||
go content = content ++ filter (`notElem` content) ls
|
go content = content ++ filter (`notElem` content) ls
|
||||||
|
@ -56,17 +56,17 @@ f `containsLines` ls = fileProperty (f ++ " contains:" ++ show ls) go f
|
||||||
-- | Ensures that a line is not present in a file.
|
-- | 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
|
-- Note that the file is ensured to exist, so if it doesn't, an empty
|
||||||
-- file will be written.
|
-- file will be written.
|
||||||
lacksLine :: FilePath -> Line -> Property
|
lacksLine :: FilePath -> Line -> Property NoInfo
|
||||||
f `lacksLine` l = fileProperty (f ++ " remove: " ++ l) (filter (/= l)) f
|
f `lacksLine` l = fileProperty (f ++ " remove: " ++ l) (filter (/= l)) f
|
||||||
|
|
||||||
-- | Removes a file. Does not remove symlinks or non-plain-files.
|
-- | Removes a file. Does not remove symlinks or non-plain-files.
|
||||||
notPresent :: FilePath -> Property
|
notPresent :: FilePath -> Property NoInfo
|
||||||
notPresent f = check (doesFileExist f) $ property (f ++ " not present") $
|
notPresent f = check (doesFileExist f) $ property (f ++ " not present") $
|
||||||
makeChange $ nukeFile f
|
makeChange $ nukeFile f
|
||||||
|
|
||||||
fileProperty :: Desc -> ([Line] -> [Line]) -> FilePath -> Property
|
fileProperty :: Desc -> ([Line] -> [Line]) -> FilePath -> Property NoInfo
|
||||||
fileProperty = fileProperty' writeFile
|
fileProperty = fileProperty' writeFile
|
||||||
fileProperty' :: (FilePath -> String -> IO ()) -> Desc -> ([Line] -> [Line]) -> FilePath -> Property
|
fileProperty' :: (FilePath -> String -> IO ()) -> Desc -> ([Line] -> [Line]) -> FilePath -> Property NoInfo
|
||||||
fileProperty' writer desc a f = property desc $ go =<< liftIO (doesFileExist f)
|
fileProperty' writer desc a f = property desc $ go =<< liftIO (doesFileExist f)
|
||||||
where
|
where
|
||||||
go True = do
|
go True = do
|
||||||
|
@ -86,12 +86,12 @@ fileProperty' writer desc a f = property desc $ go =<< liftIO (doesFileExist f)
|
||||||
setOwnerAndGroup f' (fileOwner s) (fileGroup s)
|
setOwnerAndGroup f' (fileOwner s) (fileGroup s)
|
||||||
|
|
||||||
-- | Ensures a directory exists.
|
-- | Ensures a directory exists.
|
||||||
dirExists :: FilePath -> Property
|
dirExists :: FilePath -> Property NoInfo
|
||||||
dirExists d = check (not <$> doesDirectoryExist d) $ property (d ++ " exists") $
|
dirExists d = check (not <$> doesDirectoryExist d) $ property (d ++ " exists") $
|
||||||
makeChange $ createDirectoryIfMissing True d
|
makeChange $ createDirectoryIfMissing True d
|
||||||
|
|
||||||
-- | Ensures that a file/dir has the specified owner and group.
|
-- | Ensures that a file/dir has the specified owner and group.
|
||||||
ownerGroup :: FilePath -> UserName -> GroupName -> Property
|
ownerGroup :: FilePath -> UserName -> GroupName -> Property NoInfo
|
||||||
ownerGroup f owner group = property (f ++ " owner " ++ og) $ do
|
ownerGroup f owner group = property (f ++ " owner " ++ og) $ do
|
||||||
r <- ensureProperty $ cmdProperty "chown" [og, f]
|
r <- ensureProperty $ cmdProperty "chown" [og, f]
|
||||||
if r == FailedChange
|
if r == FailedChange
|
||||||
|
@ -101,7 +101,7 @@ ownerGroup f owner group = property (f ++ " owner " ++ og) $ do
|
||||||
og = owner ++ ":" ++ group
|
og = owner ++ ":" ++ group
|
||||||
|
|
||||||
-- | Ensures that a file/dir has the specfied mode.
|
-- | Ensures that a file/dir has the specfied mode.
|
||||||
mode :: FilePath -> FileMode -> Property
|
mode :: FilePath -> FileMode -> Property NoInfo
|
||||||
mode f v = property (f ++ " mode " ++ show v) $ do
|
mode f v = property (f ++ " mode " ++ show v) $ do
|
||||||
liftIO $ modifyFileMode f (\_old -> v)
|
liftIO $ modifyFileMode f (\_old -> v)
|
||||||
noChange
|
noChange
|
||||||
|
|
|
@ -9,6 +9,7 @@ module Propellor.Property.Firewall (
|
||||||
Target(..),
|
Target(..),
|
||||||
Proto(..),
|
Proto(..),
|
||||||
Rules(..),
|
Rules(..),
|
||||||
|
Port,
|
||||||
ConnectionState(..)
|
ConnectionState(..)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
|
@ -21,10 +22,10 @@ import Utility.SafeCommand
|
||||||
import qualified Propellor.Property.Apt as Apt
|
import qualified Propellor.Property.Apt as Apt
|
||||||
import qualified Propellor.Property.Network as Network
|
import qualified Propellor.Property.Network as Network
|
||||||
|
|
||||||
installed :: Property
|
installed :: Property NoInfo
|
||||||
installed = Apt.installed ["iptables"]
|
installed = Apt.installed ["iptables"]
|
||||||
|
|
||||||
rule :: Chain -> Target -> Rules -> Property
|
rule :: Chain -> Target -> Rules -> Property NoInfo
|
||||||
rule c t rs = property ("firewall rule: " <> show r) addIpTable
|
rule c t rs = property ("firewall rule: " <> show r) addIpTable
|
||||||
where
|
where
|
||||||
r = Rule c t rs
|
r = Rule c t rs
|
||||||
|
|
|
@ -13,7 +13,7 @@ import Data.List
|
||||||
--
|
--
|
||||||
-- Note that reverting this property does not remove or stop inetd.
|
-- Note that reverting this property does not remove or stop inetd.
|
||||||
daemonRunning :: FilePath -> RevertableProperty
|
daemonRunning :: FilePath -> RevertableProperty
|
||||||
daemonRunning exportdir = RevertableProperty setup unsetup
|
daemonRunning exportdir = setup <!> unsetup
|
||||||
where
|
where
|
||||||
setup = containsLine conf (mkl "tcp4")
|
setup = containsLine conf (mkl "tcp4")
|
||||||
`requires`
|
`requires`
|
||||||
|
@ -48,7 +48,7 @@ daemonRunning exportdir = RevertableProperty setup unsetup
|
||||||
, exportdir
|
, exportdir
|
||||||
]
|
]
|
||||||
|
|
||||||
installed :: Property
|
installed :: Property NoInfo
|
||||||
installed = Apt.installed ["git"]
|
installed = Apt.installed ["git"]
|
||||||
|
|
||||||
type RepoUrl = String
|
type RepoUrl = String
|
||||||
|
@ -62,7 +62,7 @@ type Branch = String
|
||||||
-- it will be recursively deleted first.
|
-- it will be recursively deleted first.
|
||||||
--
|
--
|
||||||
-- A branch can be specified, to check out.
|
-- A branch can be specified, to check out.
|
||||||
cloned :: UserName -> RepoUrl -> FilePath -> Maybe Branch -> Property
|
cloned :: UserName -> RepoUrl -> FilePath -> Maybe Branch -> Property NoInfo
|
||||||
cloned owner url dir mbranch = check originurl (property desc checkout)
|
cloned owner url dir mbranch = check originurl (property desc checkout)
|
||||||
`requires` installed
|
`requires` installed
|
||||||
where
|
where
|
||||||
|
@ -98,7 +98,7 @@ isGitDir dir = isNothing <$> catchMaybeIO (readProcess "git" ["rev-parse", "--re
|
||||||
|
|
||||||
data GitShared = Shared GroupName | SharedAll | NotShared
|
data GitShared = Shared GroupName | SharedAll | NotShared
|
||||||
|
|
||||||
bareRepo :: FilePath -> UserName -> GitShared -> Property
|
bareRepo :: FilePath -> UserName -> GitShared -> Property NoInfo
|
||||||
bareRepo repo user gitshared = check (isRepo repo) $ propertyList ("git repo: " ++ repo) $
|
bareRepo repo user gitshared = check (isRepo repo) $ propertyList ("git repo: " ++ repo) $
|
||||||
dirExists repo : case gitshared of
|
dirExists repo : case gitshared of
|
||||||
NotShared ->
|
NotShared ->
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Utility.FileSystemEncoding
|
||||||
|
|
||||||
import System.PosixCompat
|
import System.PosixCompat
|
||||||
|
|
||||||
installed :: Property
|
installed :: Property NoInfo
|
||||||
installed = Apt.installed ["gnupg"]
|
installed = Apt.installed ["gnupg"]
|
||||||
|
|
||||||
-- A numeric id, or a description of the key, in a form understood by gpg.
|
-- A numeric id, or a description of the key, in a form understood by gpg.
|
||||||
|
@ -20,7 +20,7 @@ newtype GpgKeyId = GpgKeyId { getGpgKeyId :: String }
|
||||||
--
|
--
|
||||||
-- Recommend only using this for low-value dedicated role keys.
|
-- 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.
|
-- No attempt has been made to scrub the key out of memory once it's used.
|
||||||
keyImported :: GpgKeyId -> UserName -> Property
|
keyImported :: GpgKeyId -> UserName -> Property HasInfo
|
||||||
keyImported (GpgKeyId keyid) user = flagFile' prop genflag
|
keyImported (GpgKeyId keyid) user = flagFile' prop genflag
|
||||||
`requires` installed
|
`requires` installed
|
||||||
where
|
where
|
||||||
|
|
|
@ -4,7 +4,7 @@ import Propellor
|
||||||
|
|
||||||
type GID = Int
|
type GID = Int
|
||||||
|
|
||||||
exists :: GroupName -> Maybe GID -> Property
|
exists :: GroupName -> Maybe GID -> Property NoInfo
|
||||||
exists group' mgid = check test (cmdProperty "addgroup" $ args mgid)
|
exists group' mgid = check test (cmdProperty "addgroup" $ args mgid)
|
||||||
`describe` unwords ["group", group']
|
`describe` unwords ["group", group']
|
||||||
where
|
where
|
||||||
|
|
|
@ -4,10 +4,10 @@ import Propellor
|
||||||
import qualified Propellor.Property.File as File
|
import qualified Propellor.Property.File as File
|
||||||
import qualified Propellor.Property.Apt as Apt
|
import qualified Propellor.Property.Apt as Apt
|
||||||
|
|
||||||
-- | Eg, "hd0,0" or "xen/xvda1"
|
-- | Eg, \"hd0,0\" or \"xen/xvda1\"
|
||||||
type GrubDevice = String
|
type GrubDevice = String
|
||||||
|
|
||||||
-- | Eg, "/dev/sda"
|
-- | Eg, \"\/dev/sda\"
|
||||||
type OSDevice = String
|
type OSDevice = String
|
||||||
|
|
||||||
type TimeoutSecs = Int
|
type TimeoutSecs = Int
|
||||||
|
@ -21,7 +21,7 @@ data BIOS = PC | EFI64 | EFI32 | Coreboot | Xen
|
||||||
-- This includes running update-grub, so that the grub boot menu is
|
-- This includes running update-grub, so that the grub boot menu is
|
||||||
-- created. It will be automatically updated when kernel packages are
|
-- created. It will be automatically updated when kernel packages are
|
||||||
-- installed.
|
-- installed.
|
||||||
installed :: BIOS -> Property
|
installed :: BIOS -> Property NoInfo
|
||||||
installed bios =
|
installed bios =
|
||||||
Apt.installed [pkg] `describe` "grub package installed"
|
Apt.installed [pkg] `describe` "grub package installed"
|
||||||
`before`
|
`before`
|
||||||
|
@ -43,7 +43,7 @@ installed bios =
|
||||||
-- on the device; it always does the work to reinstall it. It's a good idea
|
-- 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
|
-- to arrange for this property to only run once, by eg making it be run
|
||||||
-- onChange after OS.cleanInstallOnce.
|
-- onChange after OS.cleanInstallOnce.
|
||||||
boots :: OSDevice -> Property
|
boots :: OSDevice -> Property NoInfo
|
||||||
boots dev = cmdProperty "grub-install" [dev]
|
boots dev = cmdProperty "grub-install" [dev]
|
||||||
`describe` ("grub boots " ++ dev)
|
`describe` ("grub boots " ++ dev)
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ boots dev = cmdProperty "grub-install" [dev]
|
||||||
--
|
--
|
||||||
-- The rootdev should be in the form "hd0", while the bootdev is in the form
|
-- The rootdev should be in the form "hd0", while the bootdev is in the form
|
||||||
-- "xen/xvda".
|
-- "xen/xvda".
|
||||||
chainPVGrub :: GrubDevice -> GrubDevice -> TimeoutSecs -> Property
|
chainPVGrub :: GrubDevice -> GrubDevice -> TimeoutSecs -> Property NoInfo
|
||||||
chainPVGrub rootdev bootdev timeout = combineProperties desc
|
chainPVGrub rootdev bootdev timeout = combineProperties desc
|
||||||
[ File.dirExists "/boot/grub"
|
[ File.dirExists "/boot/grub"
|
||||||
, "/boot/grub/menu.lst" `File.hasContent`
|
, "/boot/grub/menu.lst" `File.hasContent`
|
||||||
|
|
|
@ -6,7 +6,7 @@ import qualified Propellor.Property.File as File
|
||||||
import qualified Propellor.Property.User as User
|
import qualified Propellor.Property.User as User
|
||||||
|
|
||||||
-- Clean up a system as installed by cloudatcost.com
|
-- Clean up a system as installed by cloudatcost.com
|
||||||
decruft :: Property
|
decruft :: Property NoInfo
|
||||||
decruft = propertyList "cloudatcost cleanup"
|
decruft = propertyList "cloudatcost cleanup"
|
||||||
[ Hostname.sane
|
[ Hostname.sane
|
||||||
, "worked around grub/lvm boot bug #743126" ==>
|
, "worked around grub/lvm boot bug #743126" ==>
|
||||||
|
|
|
@ -18,7 +18,7 @@ import Data.List
|
||||||
-- If the power is cycled, the non-distro kernel still boots up.
|
-- 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,
|
-- So, this property also checks if the running kernel is present in /boot,
|
||||||
-- and if not, reboots immediately into a distro kernel.
|
-- and if not, reboots immediately into a distro kernel.
|
||||||
distroKernel :: Property
|
distroKernel :: Property NoInfo
|
||||||
distroKernel = propertyList "digital ocean distro kernel hack"
|
distroKernel = propertyList "digital ocean distro kernel hack"
|
||||||
[ Apt.installed ["grub-pc", "kexec-tools", "file"]
|
[ Apt.installed ["grub-pc", "kexec-tools", "file"]
|
||||||
, "/etc/default/kexec" `File.containsLines`
|
, "/etc/default/kexec" `File.containsLines`
|
||||||
|
|
|
@ -6,5 +6,5 @@ import qualified Propellor.Property.Grub as Grub
|
||||||
-- | Linode's pv-grub-x86_64 does not currently support booting recent
|
-- | Linode's pv-grub-x86_64 does not currently support booting recent
|
||||||
-- Debian kernels compressed with xz. This sets up pv-grub chaing to enable
|
-- Debian kernels compressed with xz. This sets up pv-grub chaing to enable
|
||||||
-- it.
|
-- it.
|
||||||
chainPVGrub :: Grub.TimeoutSecs -> Property
|
chainPVGrub :: Grub.TimeoutSecs -> Property NoInfo
|
||||||
chainPVGrub = Grub.chainPVGrub "hd0" "xen/xvda"
|
chainPVGrub = Grub.chainPVGrub "hd0" "xen/xvda"
|
||||||
|
|
|
@ -17,10 +17,10 @@ import Data.List
|
||||||
-- Also, the </etc/hosts> 127.0.0.1 line is set to localhost. Putting any
|
-- 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
|
-- other hostnames there is not best practices and can lead to annoying
|
||||||
-- messages from eg, apache.
|
-- messages from eg, apache.
|
||||||
sane :: Property
|
sane :: Property NoInfo
|
||||||
sane = property ("sane hostname") (ensureProperty . setTo =<< asks hostName)
|
sane = property ("sane hostname") (ensureProperty . setTo =<< asks hostName)
|
||||||
|
|
||||||
setTo :: HostName -> Property
|
setTo :: HostName -> Property NoInfo
|
||||||
setTo hn = combineProperties desc go
|
setTo hn = combineProperties desc go
|
||||||
where
|
where
|
||||||
desc = "hostname " ++ hn
|
desc = "hostname " ++ hn
|
||||||
|
@ -46,7 +46,7 @@ setTo hn = combineProperties desc go
|
||||||
|
|
||||||
-- | Makes </etc/resolv.conf> contain search and domain lines for
|
-- | Makes </etc/resolv.conf> contain search and domain lines for
|
||||||
-- the domain that the hostname is in.
|
-- the domain that the hostname is in.
|
||||||
searchDomain :: Property
|
searchDomain :: Property NoInfo
|
||||||
searchDomain = property desc (ensureProperty . go =<< asks hostName)
|
searchDomain = property desc (ensureProperty . go =<< asks hostName)
|
||||||
where
|
where
|
||||||
desc = "resolv.conf search and domain configured"
|
desc = "resolv.conf search and domain configured"
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
module Propellor.Property.Journald where
|
||||||
|
import Propellor
|
||||||
|
import qualified Propellor.Property.Systemd as Systemd
|
||||||
|
import Utility.DataUnits
|
||||||
|
|
||||||
|
-- | Configures journald, restarting it so the changes take effect.
|
||||||
|
configured :: Systemd.Option -> String -> Property NoInfo
|
||||||
|
configured option value =
|
||||||
|
Systemd.configured "/etc/systemd/journald.conf" option value
|
||||||
|
`onChange` Systemd.restarted "systemd-journald"
|
||||||
|
|
||||||
|
-- The string is parsed to get a data size.
|
||||||
|
-- Examples: "100 megabytes" or "0.5tb"
|
||||||
|
type DataSize = String
|
||||||
|
|
||||||
|
configuredSize :: Systemd.Option -> DataSize -> Property NoInfo
|
||||||
|
configuredSize option s = case readSize dataUnits s of
|
||||||
|
Just sz -> configured option (systemdSizeUnits sz)
|
||||||
|
Nothing -> property ("unable to parse " ++ option ++ " data size " ++ s) noChange
|
||||||
|
|
||||||
|
systemMaxUse :: DataSize -> Property NoInfo
|
||||||
|
systemMaxUse = configuredSize "SystemMaxUse"
|
||||||
|
|
||||||
|
runtimeMaxUse :: DataSize -> Property NoInfo
|
||||||
|
runtimeMaxUse = configuredSize "RuntimeMaxUse"
|
||||||
|
|
||||||
|
systemKeepFree :: DataSize -> Property NoInfo
|
||||||
|
systemKeepFree = configuredSize "SystemKeepFree"
|
||||||
|
|
||||||
|
runtimeKeepFree :: DataSize -> Property NoInfo
|
||||||
|
runtimeKeepFree = configuredSize "RuntimeKeepFree"
|
||||||
|
|
||||||
|
systemMaxFileSize :: DataSize -> Property NoInfo
|
||||||
|
systemMaxFileSize = configuredSize "SystemMaxFileSize"
|
||||||
|
|
||||||
|
runtimeMaxFileSize :: DataSize -> Property NoInfo
|
||||||
|
runtimeMaxFileSize = configuredSize "RuntimeMaxFileSize"
|
||||||
|
|
||||||
|
-- Generates size units as used in journald.conf.
|
||||||
|
systemdSizeUnits :: Integer -> String
|
||||||
|
systemdSizeUnits sz = filter (/= ' ') (roughSize cfgfileunits True sz)
|
||||||
|
where
|
||||||
|
cfgfileunits :: [Unit]
|
||||||
|
cfgfileunits =
|
||||||
|
[ Unit (p 6) "E" "exabyte"
|
||||||
|
, Unit (p 5) "P" "petabyte"
|
||||||
|
, Unit (p 4) "T" "terabyte"
|
||||||
|
, Unit (p 3) "G" "gigabyte"
|
||||||
|
, Unit (p 2) "M" "megabyte"
|
||||||
|
, Unit (p 1) "K" "kilobyte"
|
||||||
|
]
|
||||||
|
p :: Integer -> Integer
|
||||||
|
p n = 1024^n
|
|
@ -0,0 +1,63 @@
|
||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
|
||||||
|
module Propellor.Property.List (
|
||||||
|
PropertyList(..),
|
||||||
|
PropertyListType,
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Propellor.Types
|
||||||
|
import Propellor.Engine
|
||||||
|
import Propellor.PropAccum
|
||||||
|
|
||||||
|
import Data.Monoid
|
||||||
|
|
||||||
|
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 propigate 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 "foo" $ props
|
||||||
|
-- > & someproperty
|
||||||
|
-- > ! oldproperty
|
||||||
|
-- > & otherproperty
|
||||||
|
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 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 PropList where
|
||||||
|
propertyList desc = propertyList desc . getProperties
|
||||||
|
combineProperties desc = combineProperties desc . getProperties
|
||||||
|
|
||||||
|
combineSatisfy :: [Property i] -> Result -> Propellor Result
|
||||||
|
combineSatisfy [] rs = return rs
|
||||||
|
combineSatisfy (l:ls) rs = do
|
||||||
|
r <- ensureProperty $ ignoreInfo l
|
||||||
|
case r of
|
||||||
|
FailedChange -> return FailedChange
|
||||||
|
_ -> combineSatisfy ls (r <> rs)
|
|
@ -3,28 +3,93 @@ module Propellor.Property.Network where
|
||||||
import Propellor
|
import Propellor
|
||||||
import Propellor.Property.File
|
import Propellor.Property.File
|
||||||
|
|
||||||
interfaces :: FilePath
|
|
||||||
interfaces = "/etc/network/interfaces"
|
|
||||||
|
|
||||||
-- | 6to4 ipv6 connection, should work anywhere
|
|
||||||
ipv6to4 :: Property
|
|
||||||
ipv6to4 = fileProperty "ipv6to4" go interfaces
|
|
||||||
`onChange` ifUp "sit0"
|
|
||||||
where
|
|
||||||
go ls
|
|
||||||
| all (`elem` ls) stanza = ls
|
|
||||||
| otherwise = ls ++ stanza
|
|
||||||
stanza =
|
|
||||||
[ "# Automatically added by propeller"
|
|
||||||
, "iface sit0 inet6 static"
|
|
||||||
, "\taddress 2002:5044:5531::1"
|
|
||||||
, "\tnetmask 64"
|
|
||||||
, "\tgateway ::192.88.99.1"
|
|
||||||
, "auto sit0"
|
|
||||||
, "# End automatically added by propeller"
|
|
||||||
]
|
|
||||||
|
|
||||||
type Interface = String
|
type Interface = String
|
||||||
|
|
||||||
ifUp :: Interface -> Property
|
ifUp :: Interface -> Property NoInfo
|
||||||
ifUp iface = cmdProperty "ifup" [iface]
|
ifUp iface = cmdProperty "ifup" [iface]
|
||||||
|
|
||||||
|
-- | Resets /etc/network/interfaces to a clean and empty state,
|
||||||
|
-- containing just the standard loopback interface, and with
|
||||||
|
-- interfacesD enabled.
|
||||||
|
--
|
||||||
|
-- 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
|
||||||
|
[ "# Deployed by propellor, do not edit."
|
||||||
|
, ""
|
||||||
|
, "source-directory interfaces.d"
|
||||||
|
, ""
|
||||||
|
, "# The loopback network interface"
|
||||||
|
, "auto lo"
|
||||||
|
, "iface lo inet loopback"
|
||||||
|
]
|
||||||
|
`describe` ("clean " ++ interfacesFile)
|
||||||
|
|
||||||
|
-- | Writes a static interface file for the specified interface.
|
||||||
|
--
|
||||||
|
-- The interface has to be up already. It could have been brought up by
|
||||||
|
-- DHCP, or by other means. The current ipv4 addresses
|
||||||
|
-- and routing configuration of the interface are written into the file.
|
||||||
|
--
|
||||||
|
-- If the interface file already exists, this property does nothing,
|
||||||
|
-- no matter its content.
|
||||||
|
--
|
||||||
|
-- (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
|
||||||
|
where
|
||||||
|
f = interfaceDFile iface
|
||||||
|
desc = "static " ++ iface
|
||||||
|
setup = property desc $ do
|
||||||
|
ls <- liftIO $ lines <$> readProcess "ip"
|
||||||
|
["-o", "addr", "show", iface, "scope", "global"]
|
||||||
|
stanzas <- liftIO $ concat <$> mapM mkstanza ls
|
||||||
|
ensureProperty $ 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.
|
||||||
|
(_:iface':"inet":addr:_) | iface' == iface -> do
|
||||||
|
gw <- getgateway
|
||||||
|
return $ catMaybes
|
||||||
|
[ Just $ "iface " ++ iface ++ " inet static"
|
||||||
|
, Just $ "\taddress " ++ addr
|
||||||
|
, ("\tgateway " ++) <$> gw
|
||||||
|
]
|
||||||
|
_ -> return []
|
||||||
|
getgateway = do
|
||||||
|
rs <- lines <$> readProcess "ip"
|
||||||
|
["route", "show", "scope", "global", "dev", iface]
|
||||||
|
return $ case words <$> headMaybe rs of
|
||||||
|
Just ("default":"via":gw:_) -> Just gw
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
-- | 6to4 ipv6 connection, should work anywhere
|
||||||
|
ipv6to4 :: Property NoInfo
|
||||||
|
ipv6to4 = hasContent (interfaceDFile "sit0")
|
||||||
|
[ "# Deployed by propellor, do not edit."
|
||||||
|
, "iface sit0 inet6 static"
|
||||||
|
, "\taddress 2002:5044:5531::1"
|
||||||
|
, "\tnetmask 64"
|
||||||
|
, "\tgateway ::192.88.99.1"
|
||||||
|
, "auto sit0"
|
||||||
|
]
|
||||||
|
`describe` "ipv6to4"
|
||||||
|
`requires` interfacesDEnabled
|
||||||
|
`onChange` ifUp "sit0"
|
||||||
|
|
||||||
|
interfacesFile :: FilePath
|
||||||
|
interfacesFile = "/etc/network/interfaces"
|
||||||
|
|
||||||
|
-- | A file in the interfaces.d directory.
|
||||||
|
interfaceDFile :: Interface -> FilePath
|
||||||
|
interfaceDFile iface = "/etc/network/interfaces.d" </> iface
|
||||||
|
|
||||||
|
-- | 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"
|
||||||
|
|
|
@ -9,7 +9,7 @@ import System.Posix.Files
|
||||||
type ConfigFile = [String]
|
type ConfigFile = [String]
|
||||||
|
|
||||||
siteEnabled :: HostName -> ConfigFile -> RevertableProperty
|
siteEnabled :: HostName -> ConfigFile -> RevertableProperty
|
||||||
siteEnabled hn cf = RevertableProperty enable disable
|
siteEnabled hn cf = enable <!> disable
|
||||||
where
|
where
|
||||||
enable = check test prop
|
enable = check test prop
|
||||||
`describe` ("nginx site enabled " ++ hn)
|
`describe` ("nginx site enabled " ++ hn)
|
||||||
|
@ -27,7 +27,7 @@ siteEnabled hn cf = RevertableProperty enable disable
|
||||||
`requires` installed
|
`requires` installed
|
||||||
`onChange` reloaded
|
`onChange` reloaded
|
||||||
|
|
||||||
siteAvailable :: HostName -> ConfigFile -> Property
|
siteAvailable :: HostName -> ConfigFile -> Property NoInfo
|
||||||
siteAvailable hn cf = ("nginx site available " ++ hn) ==>
|
siteAvailable hn cf = ("nginx site available " ++ hn) ==>
|
||||||
siteCfg hn `File.hasContent` (comment : cf)
|
siteCfg hn `File.hasContent` (comment : cf)
|
||||||
where
|
where
|
||||||
|
@ -42,11 +42,11 @@ siteVal hn = "/etc/nginx/sites-enabled/" ++ hn
|
||||||
siteValRelativeCfg :: HostName -> FilePath
|
siteValRelativeCfg :: HostName -> FilePath
|
||||||
siteValRelativeCfg hn = "../sites-available/" ++ hn
|
siteValRelativeCfg hn = "../sites-available/" ++ hn
|
||||||
|
|
||||||
installed :: Property
|
installed :: Property NoInfo
|
||||||
installed = Apt.installed ["nginx"]
|
installed = Apt.installed ["nginx"]
|
||||||
|
|
||||||
restarted :: Property
|
restarted :: Property NoInfo
|
||||||
restarted = Service.restarted "nginx"
|
restarted = Service.restarted "nginx"
|
||||||
|
|
||||||
reloaded :: Property
|
reloaded :: Property NoInfo
|
||||||
reloaded = Service.reloaded "nginx"
|
reloaded = Service.reloaded "nginx"
|
||||||
|
|
|
@ -10,6 +10,7 @@ module Propellor.Property.OS (
|
||||||
import Propellor
|
import Propellor
|
||||||
import qualified Propellor.Property.Debootstrap as Debootstrap
|
import qualified Propellor.Property.Debootstrap as Debootstrap
|
||||||
import qualified Propellor.Property.Ssh as Ssh
|
import qualified Propellor.Property.Ssh as Ssh
|
||||||
|
import qualified Propellor.Property.Network as Network
|
||||||
import qualified Propellor.Property.User as User
|
import qualified Propellor.Property.User as User
|
||||||
import qualified Propellor.Property.File as File
|
import qualified Propellor.Property.File as File
|
||||||
import qualified Propellor.Property.Reboot as Reboot
|
import qualified Propellor.Property.Reboot as Reboot
|
||||||
|
@ -51,7 +52,7 @@ import Control.Exception (throw)
|
||||||
-- > `onChange` propertyList "fixing up after clean install"
|
-- > `onChange` propertyList "fixing up after clean install"
|
||||||
-- > [ preserveNetwork
|
-- > [ preserveNetwork
|
||||||
-- > , preserveResolvConf
|
-- > , preserveResolvConf
|
||||||
-- > , preserverRootSshAuthorized
|
-- > , preserveRootSshAuthorized
|
||||||
-- > , Apt.update
|
-- > , Apt.update
|
||||||
-- > -- , Grub.boots "/dev/sda"
|
-- > -- , Grub.boots "/dev/sda"
|
||||||
-- > -- `requires` Grub.installed Grub.PC
|
-- > -- `requires` Grub.installed Grub.PC
|
||||||
|
@ -64,7 +65,7 @@ import Control.Exception (throw)
|
||||||
-- > & User.accountFor "joey"
|
-- > & User.accountFor "joey"
|
||||||
-- > & User.hasSomePassword "joey"
|
-- > & User.hasSomePassword "joey"
|
||||||
-- > -- rest of system properties here
|
-- > -- rest of system properties here
|
||||||
cleanInstallOnce :: Confirmation -> Property
|
cleanInstallOnce :: Confirmation -> Property NoInfo
|
||||||
cleanInstallOnce confirmation = check (not <$> doesFileExist flagfile) $
|
cleanInstallOnce confirmation = check (not <$> doesFileExist flagfile) $
|
||||||
go `requires` confirmed "clean install confirmed" confirmation
|
go `requires` confirmed "clean install confirmed" confirmation
|
||||||
where
|
where
|
||||||
|
@ -88,7 +89,7 @@ cleanInstallOnce confirmation = check (not <$> doesFileExist flagfile) $
|
||||||
(Just u@(System (Ubuntu _) _)) -> debootstrap u
|
(Just u@(System (Ubuntu _) _)) -> debootstrap u
|
||||||
_ -> error "os is not declared to be Debian or Ubuntu"
|
_ -> error "os is not declared to be Debian or Ubuntu"
|
||||||
|
|
||||||
debootstrap targetos = ensureProperty $ toProp $
|
debootstrap targetos = ensureProperty $
|
||||||
-- Ignore the os setting, and install debootstrap from
|
-- Ignore the os setting, and install debootstrap from
|
||||||
-- source, since we don't know what OS we're running in yet.
|
-- source, since we don't know what OS we're running in yet.
|
||||||
Debootstrap.built' Debootstrap.sourceInstall
|
Debootstrap.built' Debootstrap.sourceInstall
|
||||||
|
@ -179,7 +180,7 @@ massRename = go []
|
||||||
|
|
||||||
data Confirmation = Confirmed HostName
|
data Confirmation = Confirmed HostName
|
||||||
|
|
||||||
confirmed :: Desc -> Confirmation -> Property
|
confirmed :: Desc -> Confirmation -> Property NoInfo
|
||||||
confirmed desc (Confirmed c) = property desc $ do
|
confirmed desc (Confirmed c) = property desc $ do
|
||||||
hostname <- asks hostName
|
hostname <- asks hostName
|
||||||
if hostname /= c
|
if hostname /= c
|
||||||
|
@ -191,11 +192,21 @@ confirmed desc (Confirmed c) = property desc $ do
|
||||||
-- | </etc/network/interfaces> is configured to bring up the network
|
-- | </etc/network/interfaces> is configured to bring up the network
|
||||||
-- interface that currently has a default route configured, using
|
-- interface that currently has a default route configured, using
|
||||||
-- the same (static) IP address.
|
-- the same (static) IP address.
|
||||||
preserveNetwork :: Property
|
preserveNetwork :: Property NoInfo
|
||||||
preserveNetwork = undefined -- TODO
|
preserveNetwork = go `requires` Network.cleanInterfacesFile
|
||||||
|
where
|
||||||
|
go = property "preserve network configuration" $ do
|
||||||
|
ls <- liftIO $ lines <$> readProcess "ip"
|
||||||
|
["route", "list", "scope", "global"]
|
||||||
|
case words <$> headMaybe ls of
|
||||||
|
Just ("default":"via":_:"dev":iface:_) ->
|
||||||
|
ensureProperty $ Network.static iface
|
||||||
|
_ -> do
|
||||||
|
warningMessage "did not find any default ipv4 route"
|
||||||
|
return FailedChange
|
||||||
|
|
||||||
-- | </etc/resolv.conf> is copied from the old OS
|
-- | </etc/resolv.conf> is copied from the old OS
|
||||||
preserveResolvConf :: Property
|
preserveResolvConf :: Property NoInfo
|
||||||
preserveResolvConf = check (fileExist oldloc) $
|
preserveResolvConf = check (fileExist oldloc) $
|
||||||
property (newloc ++ " copied from old OS") $ do
|
property (newloc ++ " copied from old OS") $ do
|
||||||
ls <- liftIO $ lines <$> readFile oldloc
|
ls <- liftIO $ lines <$> readFile oldloc
|
||||||
|
@ -207,7 +218,7 @@ preserveResolvConf = check (fileExist oldloc) $
|
||||||
-- | </root/.ssh/authorized_keys> has added to it any ssh keys that
|
-- | </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
|
-- were authorized in the old OS. Any other contents of the file are
|
||||||
-- retained.
|
-- retained.
|
||||||
preserveRootSshAuthorized :: Property
|
preserveRootSshAuthorized :: Property NoInfo
|
||||||
preserveRootSshAuthorized = check (fileExist oldloc) $
|
preserveRootSshAuthorized = check (fileExist oldloc) $
|
||||||
property (newloc ++ " copied from old OS") $ do
|
property (newloc ++ " copied from old OS") $ do
|
||||||
ks <- liftIO $ lines <$> readFile oldloc
|
ks <- liftIO $ lines <$> readFile oldloc
|
||||||
|
@ -217,7 +228,7 @@ preserveRootSshAuthorized = check (fileExist oldloc) $
|
||||||
oldloc = oldOSDir ++ newloc
|
oldloc = oldOSDir ++ newloc
|
||||||
|
|
||||||
-- Removes the old OS's backup from </old-os>
|
-- Removes the old OS's backup from </old-os>
|
||||||
oldOSRemoved :: Confirmation -> Property
|
oldOSRemoved :: Confirmation -> Property NoInfo
|
||||||
oldOSRemoved confirmation = check (doesDirectoryExist oldOSDir) $
|
oldOSRemoved confirmation = check (doesDirectoryExist oldOSDir) $
|
||||||
go `requires` confirmed "old OS backup removal confirmed" confirmation
|
go `requires` confirmed "old OS backup removal confirmed" confirmation
|
||||||
where
|
where
|
||||||
|
|
|
@ -36,7 +36,7 @@ data NumClients = OnlyClient | MultipleClients
|
||||||
-- > `requires` Ssh.keyImported SshRsa "root" (Context hostname)
|
-- > `requires` Ssh.keyImported SshRsa "root" (Context hostname)
|
||||||
--
|
--
|
||||||
-- How awesome is that?
|
-- How awesome is that?
|
||||||
backup :: FilePath -> Cron.CronTimes -> [ObnamParam] -> NumClients -> Property
|
backup :: FilePath -> Cron.CronTimes -> [ObnamParam] -> NumClients -> Property NoInfo
|
||||||
backup dir crontimes params numclients =
|
backup dir crontimes params numclients =
|
||||||
backup' dir crontimes params numclients
|
backup' dir crontimes params numclients
|
||||||
`requires` restored dir params
|
`requires` restored dir params
|
||||||
|
@ -46,7 +46,7 @@ backup dir crontimes params numclients =
|
||||||
--
|
--
|
||||||
-- The gpg secret key will be automatically imported
|
-- The gpg secret key will be automatically imported
|
||||||
-- into root's keyring using Propellor.Property.Gpg.keyImported
|
-- into root's keyring using Propellor.Property.Gpg.keyImported
|
||||||
backupEncrypted :: FilePath -> Cron.CronTimes -> [ObnamParam] -> NumClients -> Gpg.GpgKeyId -> Property
|
backupEncrypted :: FilePath -> Cron.CronTimes -> [ObnamParam] -> NumClients -> Gpg.GpgKeyId -> Property HasInfo
|
||||||
backupEncrypted dir crontimes params numclients keyid =
|
backupEncrypted dir crontimes params numclients keyid =
|
||||||
backup dir crontimes params' numclients
|
backup dir crontimes params' numclients
|
||||||
`requires` Gpg.keyImported keyid "root"
|
`requires` Gpg.keyImported keyid "root"
|
||||||
|
@ -54,7 +54,7 @@ backupEncrypted dir crontimes params numclients keyid =
|
||||||
params' = ("--encrypt-with=" ++ Gpg.getGpgKeyId keyid) : params
|
params' = ("--encrypt-with=" ++ Gpg.getGpgKeyId keyid) : params
|
||||||
|
|
||||||
-- | Does a backup, but does not automatically restore.
|
-- | Does a backup, but does not automatically restore.
|
||||||
backup' :: FilePath -> Cron.CronTimes -> [ObnamParam] -> NumClients -> Property
|
backup' :: FilePath -> Cron.CronTimes -> [ObnamParam] -> NumClients -> Property NoInfo
|
||||||
backup' dir crontimes params numclients = cronjob `describe` desc
|
backup' dir crontimes params numclients = cronjob `describe` desc
|
||||||
where
|
where
|
||||||
desc = dir ++ " backed up by obnam"
|
desc = dir ++ " backed up by obnam"
|
||||||
|
@ -80,7 +80,7 @@ backup' dir crontimes params numclients = cronjob `describe` desc
|
||||||
--
|
--
|
||||||
-- The restore is performed atomically; restoring to a temp directory
|
-- The restore is performed atomically; restoring to a temp directory
|
||||||
-- and then moving it to the directory.
|
-- and then moving it to the directory.
|
||||||
restored :: FilePath -> [ObnamParam] -> Property
|
restored :: FilePath -> [ObnamParam] -> Property NoInfo
|
||||||
restored dir params = property (dir ++ " restored by obnam") go
|
restored dir params = property (dir ++ " restored by obnam") go
|
||||||
`requires` installed
|
`requires` installed
|
||||||
where
|
where
|
||||||
|
@ -108,17 +108,17 @@ restored dir params = property (dir ++ " restored by obnam") go
|
||||||
, return FailedChange
|
, return FailedChange
|
||||||
)
|
)
|
||||||
|
|
||||||
installed :: Property
|
installed :: Property NoInfo
|
||||||
installed = Apt.installed ["obnam"]
|
installed = Apt.installed ["obnam"]
|
||||||
|
|
||||||
-- | Ensures that a recent version of obnam gets installed.
|
-- | Ensures that a recent version of obnam gets installed.
|
||||||
--
|
--
|
||||||
-- Only does anything for Debian Stable.
|
-- Only does anything for Debian Stable.
|
||||||
latestVersion :: Property
|
latestVersion :: Property NoInfo
|
||||||
latestVersion = withOS "obnam latest version" $ \o -> case o of
|
latestVersion = withOS "obnam latest version" $ \o -> case o of
|
||||||
(Just (System (Debian suite) _)) | isStable suite -> ensureProperty $
|
(Just (System (Debian suite) _)) | isStable suite -> ensureProperty $
|
||||||
Apt.setSourcesListD (stablesources suite) "obnam"
|
Apt.setSourcesListD (stablesources suite) "obnam"
|
||||||
`requires` toProp (Apt.trustsKey key)
|
`requires` Apt.trustsKey' key
|
||||||
_ -> noChange
|
_ -> noChange
|
||||||
where
|
where
|
||||||
stablesources suite =
|
stablesources suite =
|
||||||
|
|
|
@ -7,8 +7,8 @@ import qualified Propellor.Property.Service as Service
|
||||||
|
|
||||||
import Data.List
|
import Data.List
|
||||||
|
|
||||||
providerFor :: [UserName] -> String -> Property
|
providerFor :: [UserName] -> String -> Property HasInfo
|
||||||
providerFor users baseurl = propertyList desc $
|
providerFor users baseurl = propertyList desc $ map toProp
|
||||||
[ Apt.serviceInstalledRunning "apache2"
|
[ Apt.serviceInstalledRunning "apache2"
|
||||||
, Apt.installed ["simpleid"]
|
, Apt.installed ["simpleid"]
|
||||||
`onChange` Service.restarted "apache2"
|
`onChange` Service.restarted "apache2"
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
|
||||||
module Propellor.Property.Postfix where
|
module Propellor.Property.Postfix where
|
||||||
|
|
||||||
import Propellor
|
import Propellor
|
||||||
|
@ -9,13 +11,13 @@ import qualified Data.Map as M
|
||||||
import Data.List
|
import Data.List
|
||||||
import Data.Char
|
import Data.Char
|
||||||
|
|
||||||
installed :: Property
|
installed :: Property NoInfo
|
||||||
installed = Apt.serviceInstalledRunning "postfix"
|
installed = Apt.serviceInstalledRunning "postfix"
|
||||||
|
|
||||||
restarted :: Property
|
restarted :: Property NoInfo
|
||||||
restarted = Service.restarted "postfix"
|
restarted = Service.restarted "postfix"
|
||||||
|
|
||||||
reloaded :: Property
|
reloaded :: Property NoInfo
|
||||||
reloaded = Service.reloaded "postfix"
|
reloaded = Service.reloaded "postfix"
|
||||||
|
|
||||||
-- | Configures postfix as a satellite system, which
|
-- | Configures postfix as a satellite system, which
|
||||||
|
@ -24,7 +26,7 @@ reloaded = Service.reloaded "postfix"
|
||||||
-- The smarthost may refuse to relay mail on to other domains, without
|
-- The smarthost may refuse to relay mail on to other domains, without
|
||||||
-- futher coniguration/keys. But this should be enough to get cron job
|
-- futher coniguration/keys. But this should be enough to get cron job
|
||||||
-- mail flowing to a place where it will be seen.
|
-- mail flowing to a place where it will be seen.
|
||||||
satellite :: Property
|
satellite :: Property NoInfo
|
||||||
satellite = check (not <$> mainCfIsSet "relayhost") setup
|
satellite = check (not <$> mainCfIsSet "relayhost") setup
|
||||||
`requires` installed
|
`requires` installed
|
||||||
where
|
where
|
||||||
|
@ -45,13 +47,17 @@ satellite = check (not <$> mainCfIsSet "relayhost") setup
|
||||||
-- | Sets up a file by running a property (which the filename is passed
|
-- | 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
|
-- to). If the setup property makes a change, postmap will be run on the
|
||||||
-- file, and postfix will be reloaded.
|
-- file, and postfix will be reloaded.
|
||||||
mappedFile :: FilePath -> (FilePath -> Property) -> Property
|
mappedFile
|
||||||
|
:: Combines (Property x) (Property NoInfo)
|
||||||
|
=> FilePath
|
||||||
|
-> (FilePath -> Property x)
|
||||||
|
-> Property (CInfo x NoInfo)
|
||||||
mappedFile f setup = setup f
|
mappedFile f setup = setup f
|
||||||
`onChange` cmdProperty "postmap" [f]
|
`onChange` cmdProperty "postmap" [f]
|
||||||
|
|
||||||
-- | Run newaliases command, which should be done after changing
|
-- | Run newaliases command, which should be done after changing
|
||||||
-- </etc/aliases>.
|
-- </etc/aliases>.
|
||||||
newaliases :: Property
|
newaliases :: Property NoInfo
|
||||||
newaliases = trivial $ cmdProperty "newaliases" []
|
newaliases = trivial $ cmdProperty "newaliases" []
|
||||||
|
|
||||||
-- | The main config file for postfix.
|
-- | The main config file for postfix.
|
||||||
|
@ -59,7 +65,7 @@ mainCfFile :: FilePath
|
||||||
mainCfFile = "/etc/postfix/main.cf"
|
mainCfFile = "/etc/postfix/main.cf"
|
||||||
|
|
||||||
-- | Sets a main.cf name=value pair. Does not reload postfix immediately.
|
-- | Sets a main.cf name=value pair. Does not reload postfix immediately.
|
||||||
mainCf :: (String, String) -> Property
|
mainCf :: (String, String) -> Property NoInfo
|
||||||
mainCf (name, value) = check notset set
|
mainCf (name, value) = check notset set
|
||||||
`describe` ("postfix main.cf " ++ setting)
|
`describe` ("postfix main.cf " ++ setting)
|
||||||
where
|
where
|
||||||
|
@ -77,8 +83,8 @@ getMainCf name = parse . lines <$> readProcess "postconf" [name]
|
||||||
(_, v) -> v
|
(_, v) -> v
|
||||||
parse [] = Nothing
|
parse [] = Nothing
|
||||||
|
|
||||||
-- | Checks if a main.cf field is set. A field that is set to ""
|
-- | Checks if a main.cf field is set. A field that is set to
|
||||||
-- is considered not set.
|
-- the empty string is considered not set.
|
||||||
mainCfIsSet :: String -> IO Bool
|
mainCfIsSet :: String -> IO Bool
|
||||||
mainCfIsSet name = do
|
mainCfIsSet name = do
|
||||||
v <- getMainCf name
|
v <- getMainCf name
|
||||||
|
@ -96,7 +102,7 @@ mainCfIsSet name = do
|
||||||
--
|
--
|
||||||
-- Note that multiline configurations that continue onto the next line
|
-- Note that multiline configurations that continue onto the next line
|
||||||
-- are not currently supported.
|
-- are not currently supported.
|
||||||
dedupMainCf :: Property
|
dedupMainCf :: Property NoInfo
|
||||||
dedupMainCf = fileProperty "postfix main.cf dedupped" dedupCf mainCfFile
|
dedupMainCf = fileProperty "postfix main.cf dedupped" dedupCf mainCfFile
|
||||||
|
|
||||||
dedupCf :: [String] -> [String]
|
dedupCf :: [String] -> [String]
|
||||||
|
|
|
@ -11,7 +11,7 @@ type ConfigFile = [String]
|
||||||
type Conf = String
|
type Conf = String
|
||||||
|
|
||||||
confEnabled :: Conf -> ConfigFile -> RevertableProperty
|
confEnabled :: Conf -> ConfigFile -> RevertableProperty
|
||||||
confEnabled conf cf = RevertableProperty enable disable
|
confEnabled conf cf = enable <!> disable
|
||||||
where
|
where
|
||||||
enable = check test prop
|
enable = check test prop
|
||||||
`describe` ("prosody conf enabled " ++ conf)
|
`describe` ("prosody conf enabled " ++ conf)
|
||||||
|
@ -30,7 +30,7 @@ confEnabled conf cf = RevertableProperty enable disable
|
||||||
`requires` installed
|
`requires` installed
|
||||||
`onChange` reloaded
|
`onChange` reloaded
|
||||||
|
|
||||||
confAvailable :: Conf -> ConfigFile -> Property
|
confAvailable :: Conf -> ConfigFile -> Property NoInfo
|
||||||
confAvailable conf cf = ("prosody conf available " ++ conf) ==>
|
confAvailable conf cf = ("prosody conf available " ++ conf) ==>
|
||||||
confAvailPath conf `File.hasContent` (comment : cf)
|
confAvailPath conf `File.hasContent` (comment : cf)
|
||||||
where
|
where
|
||||||
|
@ -42,11 +42,11 @@ confAvailPath conf = "/etc/prosody/conf.avail" </> conf <.> "cfg.lua"
|
||||||
confValPath :: Conf -> FilePath
|
confValPath :: Conf -> FilePath
|
||||||
confValPath conf = "/etc/prosody/conf.d" </> conf <.> "cfg.lua"
|
confValPath conf = "/etc/prosody/conf.d" </> conf <.> "cfg.lua"
|
||||||
|
|
||||||
installed :: Property
|
installed :: Property NoInfo
|
||||||
installed = Apt.installed ["prosody"]
|
installed = Apt.installed ["prosody"]
|
||||||
|
|
||||||
restarted :: Property
|
restarted :: Property NoInfo
|
||||||
restarted = Service.restarted "prosody"
|
restarted = Service.restarted "prosody"
|
||||||
|
|
||||||
reloaded :: Property
|
reloaded :: Property NoInfo
|
||||||
reloaded = Service.reloaded "prosody"
|
reloaded = Service.reloaded "prosody"
|
||||||
|
|
|
@ -3,7 +3,7 @@ module Propellor.Property.Reboot where
|
||||||
import Propellor
|
import Propellor
|
||||||
import Utility.SafeCommand
|
import Utility.SafeCommand
|
||||||
|
|
||||||
now :: Property
|
now :: Property NoInfo
|
||||||
now = cmdProperty "reboot" []
|
now = cmdProperty "reboot" []
|
||||||
`describe` "reboot now"
|
`describe` "reboot now"
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ now = cmdProperty "reboot" []
|
||||||
--
|
--
|
||||||
-- The reboot can be forced to run, which bypasses the init system. Useful
|
-- The reboot can be forced to run, which bypasses the init system. Useful
|
||||||
-- if the init system might not be running for some reason.
|
-- if the init system might not be running for some reason.
|
||||||
atEnd :: Bool -> (Result -> Bool) -> Property
|
atEnd :: Bool -> (Result -> Bool) -> Property NoInfo
|
||||||
atEnd force resultok = property "scheduled reboot at end of propellor run" $ do
|
atEnd force resultok = property "scheduled reboot at end of propellor run" $ do
|
||||||
endAction "rebooting" atend
|
endAction "rebooting" atend
|
||||||
return NoChange
|
return NoChange
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
|
||||||
module Propellor.Property.Scheduled
|
module Propellor.Property.Scheduled
|
||||||
( period
|
( period
|
||||||
, periodParse
|
, periodParse
|
||||||
|
@ -18,8 +20,8 @@ import qualified Data.Map as M
|
||||||
--
|
--
|
||||||
-- This uses the description of the Property to keep track of when it was
|
-- This uses the description of the Property to keep track of when it was
|
||||||
-- last run.
|
-- last run.
|
||||||
period :: Property -> Recurrance -> Property
|
period :: (IsProp (Property i)) => Property i -> Recurrance -> Property i
|
||||||
period prop recurrance = flip describe desc $ adjustProperty prop $ \satisfy -> do
|
period prop recurrance = flip describe desc $ adjustPropertySatisfy prop $ \satisfy -> do
|
||||||
lasttime <- liftIO $ getLastChecked (propertyDesc prop)
|
lasttime <- liftIO $ getLastChecked (propertyDesc prop)
|
||||||
nexttime <- liftIO $ fmap startTime <$> nextTime schedule lasttime
|
nexttime <- liftIO $ fmap startTime <$> nextTime schedule lasttime
|
||||||
t <- liftIO localNow
|
t <- liftIO localNow
|
||||||
|
@ -34,7 +36,7 @@ period prop recurrance = flip describe desc $ adjustProperty prop $ \satisfy ->
|
||||||
desc = propertyDesc prop ++ " (period " ++ fromRecurrance recurrance ++ ")"
|
desc = propertyDesc prop ++ " (period " ++ fromRecurrance recurrance ++ ")"
|
||||||
|
|
||||||
-- | Like period, but parse a human-friendly string.
|
-- | Like period, but parse a human-friendly string.
|
||||||
periodParse :: Property -> String -> Property
|
periodParse :: Property NoInfo -> String -> Property NoInfo
|
||||||
periodParse prop s = case toRecurrance s of
|
periodParse prop s = case toRecurrance s of
|
||||||
Just recurrance -> period prop recurrance
|
Just recurrance -> period prop recurrance
|
||||||
Nothing -> property "periodParse" $ do
|
Nothing -> property "periodParse" $ do
|
||||||
|
|
|
@ -12,16 +12,16 @@ type ServiceName = String
|
||||||
-- Note that due to the general poor state of init scripts, the best
|
-- 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
|
-- we can do is try to start the service, and if it fails, assume
|
||||||
-- this means it's already running.
|
-- this means it's already running.
|
||||||
running :: ServiceName -> Property
|
running :: ServiceName -> Property NoInfo
|
||||||
running = signaled "start" "running"
|
running = signaled "start" "running"
|
||||||
|
|
||||||
restarted :: ServiceName -> Property
|
restarted :: ServiceName -> Property NoInfo
|
||||||
restarted = signaled "restart" "restarted"
|
restarted = signaled "restart" "restarted"
|
||||||
|
|
||||||
reloaded :: ServiceName -> Property
|
reloaded :: ServiceName -> Property NoInfo
|
||||||
reloaded = signaled "reload" "reloaded"
|
reloaded = signaled "reload" "reloaded"
|
||||||
|
|
||||||
signaled :: String -> Desc -> ServiceName -> Property
|
signaled :: String -> Desc -> ServiceName -> Property NoInfo
|
||||||
signaled cmd desc svc = property (desc ++ " " ++ svc) $ do
|
signaled cmd desc svc = property (desc ++ " " ++ svc) $ do
|
||||||
void $ ensureProperty $
|
void $ ensureProperty $
|
||||||
scriptProperty ["service " ++ shellEscape svc ++ " " ++ cmd ++ " >/dev/null 2>&1 || true"]
|
scriptProperty ["service " ++ shellEscape svc ++ " " ++ cmd ++ " >/dev/null 2>&1 || true"]
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
|
||||||
module Propellor.Property.SiteSpecific.GitAnnexBuilder where
|
module Propellor.Property.SiteSpecific.GitAnnexBuilder where
|
||||||
|
|
||||||
import Propellor
|
import Propellor
|
||||||
|
@ -23,54 +25,56 @@ builddir = gitbuilderdir </> "build"
|
||||||
|
|
||||||
type TimeOut = String -- eg, 5h
|
type TimeOut = String -- eg, 5h
|
||||||
|
|
||||||
autobuilder :: Architecture -> CronTimes -> TimeOut -> Property
|
autobuilder :: Architecture -> CronTimes -> TimeOut -> Property HasInfo
|
||||||
autobuilder arch crontimes timeout = combineProperties "gitannexbuilder"
|
autobuilder arch crontimes timeout = combineProperties "gitannexbuilder" $ props
|
||||||
[ Apt.serviceInstalledRunning "cron"
|
& Apt.serviceInstalledRunning "cron"
|
||||||
, Cron.niceJob "gitannexbuilder" crontimes builduser gitbuilderdir $
|
& Cron.niceJob "gitannexbuilder" crontimes builduser gitbuilderdir
|
||||||
"git pull ; timeout " ++ timeout ++ " ./autobuild"
|
("git pull ; timeout " ++ timeout ++ " ./autobuild")
|
||||||
|
& rsyncpassword
|
||||||
|
where
|
||||||
|
context = Context ("gitannexbuilder " ++ arch)
|
||||||
|
pwfile = homedir </> "rsyncpassword"
|
||||||
-- The builduser account does not have a password set,
|
-- The builduser account does not have a password set,
|
||||||
-- instead use the password privdata to hold the rsync server
|
-- instead use the password privdata to hold the rsync server
|
||||||
-- password used to upload the built image.
|
-- password used to upload the built image.
|
||||||
, withPrivData (Password builduser) context $ \getpw ->
|
rsyncpassword = withPrivData (Password builduser) context $ \getpw ->
|
||||||
property "rsync password" $ getpw $ \pw -> do
|
property "rsync password" $ getpw $ \pw -> do
|
||||||
oldpw <- liftIO $ catchDefaultIO "" $
|
oldpw <- liftIO $ catchDefaultIO "" $
|
||||||
readFileStrict pwfile
|
readFileStrict pwfile
|
||||||
if pw /= oldpw
|
if pw /= oldpw
|
||||||
then makeChange $ writeFile pwfile pw
|
then makeChange $ writeFile pwfile pw
|
||||||
else noChange
|
else noChange
|
||||||
]
|
|
||||||
where
|
|
||||||
context = Context ("gitannexbuilder " ++ arch)
|
|
||||||
pwfile = homedir </> "rsyncpassword"
|
|
||||||
|
|
||||||
tree :: Architecture -> Property
|
tree :: Architecture -> Property HasInfo
|
||||||
tree buildarch = combineProperties "gitannexbuilder tree"
|
tree buildarch = combineProperties "gitannexbuilder tree" $ props
|
||||||
[ Apt.installed ["git"]
|
& Apt.installed ["git"]
|
||||||
-- gitbuilderdir directory already exists when docker volume is used,
|
-- gitbuilderdir directory already exists when docker volume is used,
|
||||||
-- but with wrong owner.
|
-- but with wrong owner.
|
||||||
, File.dirExists gitbuilderdir
|
& File.dirExists gitbuilderdir
|
||||||
, File.ownerGroup gitbuilderdir builduser builduser
|
& File.ownerGroup gitbuilderdir builduser builduser
|
||||||
, check (not <$> (doesDirectoryExist (gitbuilderdir </> ".git"))) $
|
& gitannexbuildercloned
|
||||||
|
& builddircloned
|
||||||
|
where
|
||||||
|
gitannexbuildercloned = check (not <$> (doesDirectoryExist (gitbuilderdir </> ".git"))) $
|
||||||
userScriptProperty builduser
|
userScriptProperty builduser
|
||||||
[ "git clone git://git.kitenet.net/gitannexbuilder " ++ gitbuilderdir
|
[ "git clone git://git.kitenet.net/gitannexbuilder " ++ gitbuilderdir
|
||||||
, "cd " ++ gitbuilderdir
|
, "cd " ++ gitbuilderdir
|
||||||
, "git checkout " ++ buildarch
|
, "git checkout " ++ buildarch
|
||||||
]
|
]
|
||||||
`describe` "gitbuilder setup"
|
`describe` "gitbuilder setup"
|
||||||
, check (not <$> doesDirectoryExist builddir) $ userScriptProperty builduser
|
builddircloned = check (not <$> doesDirectoryExist builddir) $ userScriptProperty builduser
|
||||||
[ "git clone git://git-annex.branchable.com/ " ++ builddir
|
[ "git clone git://git-annex.branchable.com/ " ++ builddir
|
||||||
]
|
]
|
||||||
]
|
|
||||||
|
|
||||||
buildDepsApt :: Property
|
buildDepsApt :: Property HasInfo
|
||||||
buildDepsApt = combineProperties "gitannexbuilder build deps"
|
buildDepsApt = combineProperties "gitannexbuilder build deps" $ props
|
||||||
[ Apt.buildDep ["git-annex"]
|
& Apt.buildDep ["git-annex"]
|
||||||
, Apt.installed ["liblockfile-simple-perl"]
|
& Apt.installed ["liblockfile-simple-perl"]
|
||||||
, buildDepsNoHaskellLibs
|
& buildDepsNoHaskellLibs
|
||||||
, "git-annex source build deps installed" ==> Apt.buildDepIn builddir
|
& Apt.buildDepIn builddir
|
||||||
]
|
`describe` "git-annex source build deps installed"
|
||||||
|
|
||||||
buildDepsNoHaskellLibs :: Property
|
buildDepsNoHaskellLibs :: Property NoInfo
|
||||||
buildDepsNoHaskellLibs = Apt.installed
|
buildDepsNoHaskellLibs = Apt.installed
|
||||||
["git", "rsync", "moreutils", "ca-certificates",
|
["git", "rsync", "moreutils", "ca-certificates",
|
||||||
"debhelper", "ghc", "curl", "openssh-client", "git-remote-gcrypt",
|
"debhelper", "ghc", "curl", "openssh-client", "git-remote-gcrypt",
|
||||||
|
@ -82,7 +86,7 @@ buildDepsNoHaskellLibs = Apt.installed
|
||||||
|
|
||||||
-- Installs current versions of git-annex's deps from cabal, but only
|
-- Installs current versions of git-annex's deps from cabal, but only
|
||||||
-- does so once.
|
-- does so once.
|
||||||
cabalDeps :: Property
|
cabalDeps :: Property NoInfo
|
||||||
cabalDeps = flagFile go cabalupdated
|
cabalDeps = flagFile go cabalupdated
|
||||||
where
|
where
|
||||||
go = userScriptProperty builduser ["cabal update && cabal install git-annex --only-dependencies || true"]
|
go = userScriptProperty builduser ["cabal update && cabal install git-annex --only-dependencies || true"]
|
||||||
|
@ -108,7 +112,13 @@ androidAutoBuilderContainer dockerImage crontimes timeout =
|
||||||
& autobuilder "android" crontimes timeout
|
& autobuilder "android" crontimes timeout
|
||||||
|
|
||||||
-- Android is cross-built in a Debian i386 container, using the Android NDK.
|
-- Android is cross-built in a Debian i386 container, using the Android NDK.
|
||||||
androidContainer :: (System -> Docker.Image) -> Docker.ContainerName -> Property -> FilePath -> Docker.Container
|
androidContainer
|
||||||
|
:: (IsProp (Property (CInfo NoInfo i)), (Combines (Property NoInfo) (Property i)))
|
||||||
|
=> (System -> Docker.Image)
|
||||||
|
-> Docker.ContainerName
|
||||||
|
-> Property i
|
||||||
|
-> FilePath
|
||||||
|
-> Docker.Container
|
||||||
androidContainer dockerImage name setupgitannexdir gitannexdir = Docker.container name
|
androidContainer dockerImage name setupgitannexdir gitannexdir = Docker.container name
|
||||||
(dockerImage osver)
|
(dockerImage osver)
|
||||||
& os osver
|
& os osver
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Propellor.Property.User
|
||||||
import Utility.SafeCommand
|
import Utility.SafeCommand
|
||||||
|
|
||||||
-- | Clones Joey Hess's git home directory, and runs its fixups script.
|
-- | Clones Joey Hess's git home directory, and runs its fixups script.
|
||||||
installedFor :: UserName -> Property
|
installedFor :: UserName -> Property NoInfo
|
||||||
installedFor user = check (not <$> hasGitDir user) $
|
installedFor user = check (not <$> hasGitDir user) $
|
||||||
property ("githome " ++ user) (go =<< liftIO (homedir user))
|
property ("githome " ++ user) (go =<< liftIO (homedir user))
|
||||||
`requires` Apt.installed ["git"]
|
`requires` Apt.installed ["git"]
|
||||||
|
|
|
@ -22,22 +22,18 @@ import Data.List
|
||||||
import System.Posix.Files
|
import System.Posix.Files
|
||||||
import Data.String.Utils
|
import Data.String.Utils
|
||||||
|
|
||||||
oldUseNetServer :: [Host] -> Property
|
oldUseNetServer :: [Host] -> Property HasInfo
|
||||||
oldUseNetServer hosts = propertyList ("olduse.net server")
|
oldUseNetServer hosts = propertyList "olduse.net server" $ props
|
||||||
[ oldUseNetInstalled "oldusenet-server"
|
& oldUseNetInstalled "oldusenet-server"
|
||||||
, Obnam.latestVersion
|
& Obnam.latestVersion
|
||||||
, Obnam.backup datadir "33 4 * * *"
|
& oldUseNetBackup
|
||||||
[ "--repository=sftp://2318@usw-s002.rsync.net/~/olduse.net"
|
& check (not . isSymbolicLink <$> getSymbolicLinkStatus newsspool)
|
||||||
, "--client-name=spool"
|
(property "olduse.net spool in place" $ makeChange $ do
|
||||||
] Obnam.OnlyClient
|
|
||||||
`requires` Ssh.keyImported SshRsa "root" (Context "olduse.net")
|
|
||||||
`requires` Ssh.knownHost hosts "usw-s002.rsync.net" "root"
|
|
||||||
, check (not . isSymbolicLink <$> getSymbolicLinkStatus newsspool) $
|
|
||||||
property "olduse.net spool in place" $ makeChange $ do
|
|
||||||
removeDirectoryRecursive newsspool
|
removeDirectoryRecursive newsspool
|
||||||
createSymbolicLink (datadir </> "news") newsspool
|
createSymbolicLink (datadir </> "news") newsspool
|
||||||
, Apt.installed ["leafnode"]
|
)
|
||||||
, "/etc/news/leafnode/config" `File.hasContent`
|
& Apt.installed ["leafnode"]
|
||||||
|
& "/etc/news/leafnode/config" `File.hasContent`
|
||||||
[ "# olduse.net configuration (deployed by propellor)"
|
[ "# olduse.net configuration (deployed by propellor)"
|
||||||
, "expire = 1000000" -- no expiry via texpire
|
, "expire = 1000000" -- no expiry via texpire
|
||||||
, "server = " -- no upstream server
|
, "server = " -- no upstream server
|
||||||
|
@ -45,17 +41,22 @@ oldUseNetServer hosts = propertyList ("olduse.net server")
|
||||||
, "allowSTRANGERS = 42" -- lets anyone connect
|
, "allowSTRANGERS = 42" -- lets anyone connect
|
||||||
, "nopost = 1" -- no new posting (just gather them)
|
, "nopost = 1" -- no new posting (just gather them)
|
||||||
]
|
]
|
||||||
, "/etc/hosts.deny" `File.lacksLine` "leafnode: ALL"
|
& "/etc/hosts.deny" `File.lacksLine` "leafnode: ALL"
|
||||||
, Apt.serviceInstalledRunning "openbsd-inetd"
|
& Apt.serviceInstalledRunning "openbsd-inetd"
|
||||||
, File.notPresent "/etc/cron.daily/leafnode"
|
& File.notPresent "/etc/cron.daily/leafnode"
|
||||||
, File.notPresent "/etc/cron.d/leafnode"
|
& File.notPresent "/etc/cron.d/leafnode"
|
||||||
, Cron.niceJob "oldusenet-expire" "11 1 * * *" "news" newsspool $ intercalate ";"
|
& Cron.niceJob "oldusenet-expire" "11 1 * * *" "news" newsspool expirecommand
|
||||||
|
& Cron.niceJob "oldusenet-uucp" "*/5 * * * *" "news" "/" uucpcommand
|
||||||
|
& Apache.siteEnabled "nntp.olduse.net" nntpcfg
|
||||||
|
where
|
||||||
|
newsspool = "/var/spool/news"
|
||||||
|
datadir = "/var/spool/oldusenet"
|
||||||
|
expirecommand = intercalate ";"
|
||||||
[ "find \\( -path ./out.going -or -path ./interesting.groups -or -path './*/.overview' \\) -prune -or -type f -ctime +60 -print | xargs --no-run-if-empty rm"
|
[ "find \\( -path ./out.going -or -path ./interesting.groups -or -path './*/.overview' \\) -prune -or -type f -ctime +60 -print | xargs --no-run-if-empty rm"
|
||||||
, "find -type d -empty | xargs --no-run-if-empty rmdir"
|
, "find -type d -empty | xargs --no-run-if-empty rmdir"
|
||||||
]
|
]
|
||||||
, Cron.niceJob "oldusenet-uucp" "*/5 * * * *" "news" "/" $
|
uucpcommand = "/usr/bin/uucp " ++ datadir
|
||||||
"/usr/bin/uucp " ++ datadir
|
nntpcfg = apachecfg "nntp.olduse.net" False
|
||||||
, toProp $ Apache.siteEnabled "nntp.olduse.net" $ apachecfg "nntp.olduse.net" False
|
|
||||||
[ " DocumentRoot " ++ datadir ++ "/"
|
[ " DocumentRoot " ++ datadir ++ "/"
|
||||||
, " <Directory " ++ datadir ++ "/>"
|
, " <Directory " ++ datadir ++ "/>"
|
||||||
, " Options Indexes FollowSymlinks"
|
, " Options Indexes FollowSymlinks"
|
||||||
|
@ -63,23 +64,25 @@ oldUseNetServer hosts = propertyList ("olduse.net server")
|
||||||
, Apache.allowAll
|
, Apache.allowAll
|
||||||
, " </Directory>"
|
, " </Directory>"
|
||||||
]
|
]
|
||||||
]
|
|
||||||
where
|
|
||||||
newsspool = "/var/spool/news"
|
|
||||||
datadir = "/var/spool/oldusenet"
|
|
||||||
|
|
||||||
oldUseNetShellBox :: Property
|
oldUseNetBackup = Obnam.backup datadir "33 4 * * *"
|
||||||
oldUseNetShellBox = propertyList "olduse.net shellbox"
|
[ "--repository=sftp://2318@usw-s002.rsync.net/~/olduse.net"
|
||||||
[ oldUseNetInstalled "oldusenet"
|
, "--client-name=spool"
|
||||||
, Service.running "shellinabox"
|
] Obnam.OnlyClient
|
||||||
]
|
`requires` Ssh.keyImported SshRsa "root" (Context "olduse.net")
|
||||||
|
`requires` Ssh.knownHost hosts "usw-s002.rsync.net" "root"
|
||||||
|
|
||||||
oldUseNetInstalled :: Apt.Package -> Property
|
oldUseNetShellBox :: Property HasInfo
|
||||||
|
oldUseNetShellBox = propertyList "olduse.net shellbox" $ props
|
||||||
|
& oldUseNetInstalled "oldusenet"
|
||||||
|
& Service.running "shellinabox"
|
||||||
|
|
||||||
|
oldUseNetInstalled :: Apt.Package -> Property HasInfo
|
||||||
oldUseNetInstalled pkg = check (not <$> Apt.isInstalled pkg) $
|
oldUseNetInstalled pkg = check (not <$> Apt.isInstalled pkg) $
|
||||||
propertyList ("olduse.net " ++ 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")
|
& 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")
|
||||||
`describe` "olduse.net build deps"
|
`describe` "olduse.net build deps"
|
||||||
, scriptProperty
|
& scriptProperty
|
||||||
[ "rm -rf /root/tmp/oldusenet" -- idenpotency
|
[ "rm -rf /root/tmp/oldusenet" -- idenpotency
|
||||||
, "git clone git://olduse.net/ /root/tmp/oldusenet/source"
|
, "git clone git://olduse.net/ /root/tmp/oldusenet/source"
|
||||||
, "cd /root/tmp/oldusenet/source/"
|
, "cd /root/tmp/oldusenet/source/"
|
||||||
|
@ -88,12 +91,15 @@ oldUseNetInstalled pkg = check (not <$> Apt.isInstalled pkg) $
|
||||||
, "apt-get -fy install" -- dependencies
|
, "apt-get -fy install" -- dependencies
|
||||||
, "rm -rf /root/tmp/oldusenet"
|
, "rm -rf /root/tmp/oldusenet"
|
||||||
] `describe` "olduse.net built"
|
] `describe` "olduse.net built"
|
||||||
]
|
|
||||||
|
|
||||||
|
kgbServer :: Property HasInfo
|
||||||
kgbServer :: Property
|
kgbServer = propertyList desc $ props
|
||||||
kgbServer = propertyList desc
|
& installed
|
||||||
[ withOS desc $ \o -> case o of
|
& 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
|
||||||
(Just (System (Debian Unstable) _)) ->
|
(Just (System (Debian Unstable) _)) ->
|
||||||
ensureProperty $ propertyList desc
|
ensureProperty $ propertyList desc
|
||||||
[ Apt.serviceInstalledRunning "kgb-bot"
|
[ Apt.serviceInstalledRunning "kgb-bot"
|
||||||
|
@ -102,28 +108,22 @@ kgbServer = propertyList desc
|
||||||
`onChange` Service.running "kgb-bot"
|
`onChange` Service.running "kgb-bot"
|
||||||
]
|
]
|
||||||
_ -> error "kgb server needs Debian unstable (for kgb-bot 1.31+)"
|
_ -> error "kgb server needs Debian unstable (for kgb-bot 1.31+)"
|
||||||
, File.hasPrivContent "/etc/kgb-bot/kgb.conf" anyContext
|
|
||||||
`onChange` Service.restarted "kgb-bot"
|
|
||||||
]
|
|
||||||
where
|
|
||||||
desc = "kgb.kitenet.net setup"
|
|
||||||
|
|
||||||
mumbleServer :: [Host] -> Property
|
mumbleServer :: [Host] -> Property HasInfo
|
||||||
mumbleServer hosts = combineProperties hn
|
mumbleServer hosts = combineProperties hn $ props
|
||||||
[ Apt.serviceInstalledRunning "mumble-server"
|
& Apt.serviceInstalledRunning "mumble-server"
|
||||||
, Obnam.latestVersion
|
& Obnam.latestVersion
|
||||||
, Obnam.backup "/var/lib/mumble-server" "55 5 * * *"
|
& Obnam.backup "/var/lib/mumble-server" "55 5 * * *"
|
||||||
[ "--repository=sftp://joey@usbackup.kitenet.net/~/lib/backup/" ++ hn ++ ".obnam"
|
[ "--repository=sftp://joey@usbackup.kitenet.net/~/lib/backup/" ++ hn ++ ".obnam"
|
||||||
, "--client-name=mumble"
|
, "--client-name=mumble"
|
||||||
] Obnam.OnlyClient
|
] Obnam.OnlyClient
|
||||||
`requires` Ssh.keyImported SshRsa "root" (Context hn)
|
`requires` Ssh.keyImported SshRsa "root" (Context hn)
|
||||||
`requires` Ssh.knownHost hosts "usbackup.kitenet.net" "root"
|
`requires` Ssh.knownHost hosts "usbackup.kitenet.net" "root"
|
||||||
, trivial $ cmdProperty "chown" ["-R", "mumble-server:mumble-server", "/var/lib/mumble-server"]
|
& trivial (cmdProperty "chown" ["-R", "mumble-server:mumble-server", "/var/lib/mumble-server"])
|
||||||
]
|
|
||||||
where
|
where
|
||||||
hn = "mumble.debian.net"
|
hn = "mumble.debian.net"
|
||||||
|
|
||||||
obnamLowMem :: Property
|
obnamLowMem :: Property NoInfo
|
||||||
obnamLowMem = combineProperties "obnam tuned for low memory use"
|
obnamLowMem = combineProperties "obnam tuned for low memory use"
|
||||||
[ Obnam.latestVersion
|
[ Obnam.latestVersion
|
||||||
, "/etc/obnam.conf" `File.containsLines`
|
, "/etc/obnam.conf" `File.containsLines`
|
||||||
|
@ -135,10 +135,10 @@ obnamLowMem = combineProperties "obnam tuned for low memory use"
|
||||||
]
|
]
|
||||||
|
|
||||||
-- git.kitenet.net and git.joeyh.name
|
-- git.kitenet.net and git.joeyh.name
|
||||||
gitServer :: [Host] -> Property
|
gitServer :: [Host] -> Property HasInfo
|
||||||
gitServer hosts = propertyList "git.kitenet.net setup"
|
gitServer hosts = propertyList "git.kitenet.net setup" $ props
|
||||||
[ Obnam.latestVersion
|
& Obnam.latestVersion
|
||||||
, Obnam.backupEncrypted "/srv/git" "33 3 * * *"
|
& Obnam.backupEncrypted "/srv/git" "33 3 * * *"
|
||||||
[ "--repository=sftp://2318@usw-s002.rsync.net/~/git.kitenet.net"
|
[ "--repository=sftp://2318@usw-s002.rsync.net/~/git.kitenet.net"
|
||||||
, "--client-name=wren" -- historical
|
, "--client-name=wren" -- historical
|
||||||
] Obnam.OnlyClient (Gpg.GpgKeyId "1B169BE1")
|
] Obnam.OnlyClient (Gpg.GpgKeyId "1B169BE1")
|
||||||
|
@ -146,14 +146,14 @@ gitServer hosts = propertyList "git.kitenet.net setup"
|
||||||
`requires` Ssh.knownHost hosts "usw-s002.rsync.net" "root"
|
`requires` Ssh.knownHost hosts "usw-s002.rsync.net" "root"
|
||||||
`requires` Ssh.authorizedKeys "family" (Context "git.kitenet.net")
|
`requires` Ssh.authorizedKeys "family" (Context "git.kitenet.net")
|
||||||
`requires` User.accountFor "family"
|
`requires` User.accountFor "family"
|
||||||
, Apt.installed ["git", "rsync", "gitweb"]
|
& Apt.installed ["git", "rsync", "gitweb"]
|
||||||
-- backport avoids channel flooding on branch merge
|
-- backport avoids channel flooding on branch merge
|
||||||
, Apt.installedBackport ["kgb-client"]
|
& Apt.installedBackport ["kgb-client"]
|
||||||
-- backport supports ssh event notification
|
-- backport supports ssh event notification
|
||||||
, Apt.installedBackport ["git-annex"]
|
& Apt.installedBackport ["git-annex"]
|
||||||
, File.hasPrivContentExposed "/etc/kgb-bot/kgb-client.conf" anyContext
|
& File.hasPrivContentExposed "/etc/kgb-bot/kgb-client.conf" anyContext
|
||||||
, toProp $ Git.daemonRunning "/srv/git"
|
& Git.daemonRunning "/srv/git"
|
||||||
, "/etc/gitweb.conf" `File.containsLines`
|
& "/etc/gitweb.conf" `File.containsLines`
|
||||||
[ "$projectroot = '/srv/git';"
|
[ "$projectroot = '/srv/git';"
|
||||||
, "@git_base_url_list = ('git://git.kitenet.net', 'http://git.kitenet.net/git', 'https://git.kitenet.net/git', 'ssh://git.kitenet.net/srv/git');"
|
, "@git_base_url_list = ('git://git.kitenet.net', 'http://git.kitenet.net/git', 'https://git.kitenet.net/git', 'ssh://git.kitenet.net/srv/git');"
|
||||||
, "# disable snapshot download; overloads server"
|
, "# disable snapshot download; overloads server"
|
||||||
|
@ -161,15 +161,14 @@ gitServer hosts = propertyList "git.kitenet.net setup"
|
||||||
]
|
]
|
||||||
`describe` "gitweb configured"
|
`describe` "gitweb configured"
|
||||||
-- Repos push on to github.
|
-- Repos push on to github.
|
||||||
, Ssh.knownHost hosts "github.com" "joey"
|
& Ssh.knownHost hosts "github.com" "joey"
|
||||||
-- I keep the website used for gitweb checked into git..
|
-- I keep the website used for gitweb checked into git..
|
||||||
, Git.cloned "root" "/srv/git/joey/git.kitenet.net.git" "/srv/web/git.kitenet.net" Nothing
|
& Git.cloned "root" "/srv/git/joey/git.kitenet.net.git" "/srv/web/git.kitenet.net" Nothing
|
||||||
, website "git.kitenet.net"
|
& website "git.kitenet.net"
|
||||||
, website "git.joeyh.name"
|
& website "git.joeyh.name"
|
||||||
, toProp $ Apache.modEnabled "cgi"
|
& Apache.modEnabled "cgi"
|
||||||
]
|
|
||||||
where
|
where
|
||||||
website hn = toProp $ Apache.siteEnabled hn $ apachecfg hn True
|
website hn = apacheSite hn True
|
||||||
[ " DocumentRoot /srv/web/git.kitenet.net/"
|
[ " DocumentRoot /srv/web/git.kitenet.net/"
|
||||||
, " <Directory /srv/web/git.kitenet.net/>"
|
, " <Directory /srv/web/git.kitenet.net/>"
|
||||||
, " Options Indexes ExecCGI FollowSymlinks"
|
, " Options Indexes ExecCGI FollowSymlinks"
|
||||||
|
@ -188,18 +187,17 @@ gitServer hosts = propertyList "git.kitenet.net setup"
|
||||||
type AnnexUUID = String
|
type AnnexUUID = String
|
||||||
|
|
||||||
-- | A website, with files coming from a git-annex repository.
|
-- | A website, with files coming from a git-annex repository.
|
||||||
annexWebSite :: Git.RepoUrl -> HostName -> AnnexUUID -> [(String, Git.RepoUrl)] -> Property
|
annexWebSite :: Git.RepoUrl -> HostName -> AnnexUUID -> [(String, Git.RepoUrl)] -> Property HasInfo
|
||||||
annexWebSite origin hn uuid remotes = propertyList (hn ++" website using git-annex")
|
annexWebSite origin hn uuid remotes = propertyList (hn ++" website using git-annex") $ props
|
||||||
[ Git.cloned "joey" origin dir Nothing
|
& Git.cloned "joey" origin dir Nothing
|
||||||
`onChange` setup
|
`onChange` setup
|
||||||
, alias hn
|
& alias hn
|
||||||
, postupdatehook `File.hasContent`
|
& postupdatehook `File.hasContent`
|
||||||
[ "#!/bin/sh"
|
[ "#!/bin/sh"
|
||||||
, "exec git update-server-info"
|
, "exec git update-server-info"
|
||||||
] `onChange`
|
] `onChange`
|
||||||
(postupdatehook `File.mode` (combineModes (ownerWriteMode:readModes ++ executeModes)))
|
(postupdatehook `File.mode` (combineModes (ownerWriteMode:readModes ++ executeModes)))
|
||||||
, setupapache
|
& setupapache
|
||||||
]
|
|
||||||
where
|
where
|
||||||
dir = "/srv/web/" ++ hn
|
dir = "/srv/web/" ++ hn
|
||||||
postupdatehook = dir </> ".git/hooks/post-update"
|
postupdatehook = dir </> ".git/hooks/post-update"
|
||||||
|
@ -212,7 +210,7 @@ annexWebSite origin hn uuid remotes = propertyList (hn ++" website using git-ann
|
||||||
, "git update-server-info"
|
, "git update-server-info"
|
||||||
]
|
]
|
||||||
addremote (name, url) = "git remote add " ++ shellEscape name ++ " " ++ shellEscape url
|
addremote (name, url) = "git remote add " ++ shellEscape name ++ " " ++ shellEscape url
|
||||||
setupapache = toProp $ Apache.siteEnabled hn $ apachecfg hn True $
|
setupapache = apacheSite hn True
|
||||||
[ " ServerAlias www."++hn
|
[ " ServerAlias www."++hn
|
||||||
, ""
|
, ""
|
||||||
, " DocumentRoot /srv/web/"++hn
|
, " DocumentRoot /srv/web/"++hn
|
||||||
|
@ -230,6 +228,9 @@ annexWebSite origin hn uuid remotes = propertyList (hn ++" website using git-ann
|
||||||
, " </Directory>"
|
, " </Directory>"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
apacheSite :: HostName -> Bool -> Apache.ConfigFile -> RevertableProperty
|
||||||
|
apacheSite hn withssl middle = Apache.siteEnabled hn $ apachecfg hn withssl middle
|
||||||
|
|
||||||
apachecfg :: HostName -> Bool -> Apache.ConfigFile -> Apache.ConfigFile
|
apachecfg :: HostName -> Bool -> Apache.ConfigFile -> Apache.ConfigFile
|
||||||
apachecfg hn withssl middle
|
apachecfg hn withssl middle
|
||||||
| withssl = vhost False ++ vhost True
|
| withssl = vhost False ++ vhost True
|
||||||
|
@ -268,20 +269,19 @@ mainhttpscert True =
|
||||||
, " SSLCertificateChainFile /etc/ssl/certs/startssl.pem"
|
, " SSLCertificateChainFile /etc/ssl/certs/startssl.pem"
|
||||||
]
|
]
|
||||||
|
|
||||||
gitAnnexDistributor :: Property
|
gitAnnexDistributor :: Property HasInfo
|
||||||
gitAnnexDistributor = combineProperties "git-annex distributor, including rsync server and signer"
|
gitAnnexDistributor = combineProperties "git-annex distributor, including rsync server and signer" $ props
|
||||||
[ Apt.installed ["rsync"]
|
& Apt.installed ["rsync"]
|
||||||
, File.hasPrivContent "/etc/rsyncd.conf" (Context "git-annex distributor")
|
& File.hasPrivContent "/etc/rsyncd.conf" (Context "git-annex distributor")
|
||||||
`onChange` Service.restarted "rsync"
|
`onChange` Service.restarted "rsync"
|
||||||
, File.hasPrivContent "/etc/rsyncd.secrets" (Context "git-annex distributor")
|
& File.hasPrivContent "/etc/rsyncd.secrets" (Context "git-annex distributor")
|
||||||
`onChange` Service.restarted "rsync"
|
`onChange` Service.restarted "rsync"
|
||||||
, "/etc/default/rsync" `File.containsLine` "RSYNC_ENABLE=true"
|
& "/etc/default/rsync" `File.containsLine` "RSYNC_ENABLE=true"
|
||||||
`onChange` Service.running "rsync"
|
`onChange` Service.running "rsync"
|
||||||
, endpoint "/srv/web/downloads.kitenet.net/git-annex/autobuild"
|
& endpoint "/srv/web/downloads.kitenet.net/git-annex/autobuild"
|
||||||
, endpoint "/srv/web/downloads.kitenet.net/git-annex/autobuild/x86_64-apple-mavericks"
|
& endpoint "/srv/web/downloads.kitenet.net/git-annex/autobuild/x86_64-apple-mavericks"
|
||||||
-- git-annex distribution signing key
|
-- git-annex distribution signing key
|
||||||
, Gpg.keyImported (Gpg.GpgKeyId "89C809CB") "joey"
|
& Gpg.keyImported (Gpg.GpgKeyId "89C809CB") "joey"
|
||||||
]
|
|
||||||
where
|
where
|
||||||
endpoint d = combineProperties ("endpoint " ++ d)
|
endpoint d = combineProperties ("endpoint " ++ d)
|
||||||
[ File.dirExists d
|
[ File.dirExists d
|
||||||
|
@ -289,50 +289,48 @@ gitAnnexDistributor = combineProperties "git-annex distributor, including rsync
|
||||||
]
|
]
|
||||||
|
|
||||||
-- Twitter, you kill us.
|
-- Twitter, you kill us.
|
||||||
twitRss :: Property
|
twitRss :: Property HasInfo
|
||||||
twitRss = combineProperties "twitter rss"
|
twitRss = combineProperties "twitter rss" $ props
|
||||||
[ Git.cloned "joey" "git://git.kitenet.net/twitrss.git" dir Nothing
|
& Git.cloned "joey" "git://git.kitenet.net/twitrss.git" dir Nothing
|
||||||
, check (not <$> doesFileExist (dir </> "twitRss")) $
|
& check (not <$> doesFileExist (dir </> "twitRss")) compiled
|
||||||
userScriptProperty "joey"
|
& feed "http://twitter.com/search/realtime?q=git-annex" "git-annex-twitter"
|
||||||
[ "cd " ++ dir
|
& feed "http://twitter.com/search/realtime?q=olduse+OR+git-annex+OR+debhelper+OR+etckeeper+OR+ikiwiki+-ashley_ikiwiki" "twittergrep"
|
||||||
, "ghc --make twitRss"
|
|
||||||
]
|
|
||||||
`requires` Apt.installed
|
|
||||||
[ "libghc-xml-dev"
|
|
||||||
, "libghc-feed-dev"
|
|
||||||
, "libghc-tagsoup-dev"
|
|
||||||
]
|
|
||||||
, feed "http://twitter.com/search/realtime?q=git-annex" "git-annex-twitter"
|
|
||||||
, feed "http://twitter.com/search/realtime?q=olduse+OR+git-annex+OR+debhelper+OR+etckeeper+OR+ikiwiki+-ashley_ikiwiki" "twittergrep"
|
|
||||||
]
|
|
||||||
where
|
where
|
||||||
dir = "/srv/web/tmp.kitenet.net/twitrss"
|
dir = "/srv/web/tmp.kitenet.net/twitrss"
|
||||||
crontime = "15 * * * *"
|
crontime = "15 * * * *"
|
||||||
feed url desc = Cron.job desc crontime "joey" dir $
|
feed url desc = Cron.job desc crontime "joey" dir $
|
||||||
"./twitRss " ++ shellEscape url ++ " > " ++ shellEscape ("../" ++ desc ++ ".rss")
|
"./twitRss " ++ shellEscape url ++ " > " ++ shellEscape ("../" ++ desc ++ ".rss")
|
||||||
|
compiled = userScriptProperty "joey"
|
||||||
|
[ "cd " ++ dir
|
||||||
|
, "ghc --make twitRss"
|
||||||
|
]
|
||||||
|
`requires` Apt.installed
|
||||||
|
[ "libghc-xml-dev"
|
||||||
|
, "libghc-feed-dev"
|
||||||
|
, "libghc-tagsoup-dev"
|
||||||
|
]
|
||||||
|
|
||||||
-- Work around for expired ssl cert.
|
-- Work around for expired ssl cert.
|
||||||
-- (no longer expired, TODO remove this and change urls)
|
-- (no longer expired, TODO remove this and change urls)
|
||||||
pumpRss :: Property
|
pumpRss :: Property NoInfo
|
||||||
pumpRss = Cron.job "pump rss" "15 * * * *" "joey" "/srv/web/tmp.kitenet.net/"
|
pumpRss = Cron.job "pump rss" "15 * * * *" "joey" "/srv/web/tmp.kitenet.net/"
|
||||||
"wget https://pump2rss.com/feed/joeyh@identi.ca.atom -O pump.atom --no-check-certificate 2>/dev/null"
|
"wget https://pump2rss.com/feed/joeyh@identi.ca.atom -O pump.atom --no-check-certificate 2>/dev/null"
|
||||||
|
|
||||||
ircBouncer :: Property
|
ircBouncer :: Property HasInfo
|
||||||
ircBouncer = propertyList "IRC bouncer"
|
ircBouncer = propertyList "IRC bouncer" $ props
|
||||||
[ Apt.installed ["znc"]
|
& Apt.installed ["znc"]
|
||||||
, User.accountFor "znc"
|
& User.accountFor "znc"
|
||||||
, File.dirExists (takeDirectory conf)
|
& File.dirExists (takeDirectory conf)
|
||||||
, File.hasPrivContent conf anyContext
|
& File.hasPrivContent conf anyContext
|
||||||
, File.ownerGroup conf "znc" "znc"
|
& File.ownerGroup conf "znc" "znc"
|
||||||
, Cron.job "znconboot" "@reboot" "znc" "~" "znc"
|
& Cron.job "znconboot" "@reboot" "znc" "~" "znc"
|
||||||
-- ensure running if it was not already
|
-- ensure running if it was not already
|
||||||
, trivial $ userScriptProperty "znc" ["znc || true"]
|
& trivial (userScriptProperty "znc" ["znc || true"])
|
||||||
`describe` "znc running"
|
`describe` "znc running"
|
||||||
]
|
|
||||||
where
|
where
|
||||||
conf = "/home/znc/.znc/configs/znc.conf"
|
conf = "/home/znc/.znc/configs/znc.conf"
|
||||||
|
|
||||||
kiteShellBox :: Property
|
kiteShellBox :: Property NoInfo
|
||||||
kiteShellBox = propertyList "kitenet.net shellinabox"
|
kiteShellBox = propertyList "kitenet.net shellinabox"
|
||||||
[ Apt.installed ["shellinabox"]
|
[ Apt.installed ["shellinabox"]
|
||||||
, File.hasContent "/etc/default/shellinabox"
|
, File.hasContent "/etc/default/shellinabox"
|
||||||
|
@ -345,28 +343,34 @@ kiteShellBox = propertyList "kitenet.net shellinabox"
|
||||||
, Service.running "shellinabox"
|
, Service.running "shellinabox"
|
||||||
]
|
]
|
||||||
|
|
||||||
githubBackup :: Property
|
githubBackup :: Property HasInfo
|
||||||
githubBackup = propertyList "github-backup box"
|
githubBackup = propertyList "github-backup box" $ props
|
||||||
[ Apt.installed ["github-backup", "moreutils"]
|
& Apt.installed ["github-backup", "moreutils"]
|
||||||
, let f = "/home/joey/.github-keys"
|
& githubKeys
|
||||||
in File.hasPrivContent f anyContext
|
& Cron.niceJob "github-backup run" "30 4 * * *" "joey"
|
||||||
`onChange` File.ownerGroup f "joey" "joey"
|
"/home/joey/lib/backup" backupcmd
|
||||||
, Cron.niceJob "github-backup run" "30 4 * * *" "joey"
|
& Cron.niceJob "gitriddance" "30 4 * * *" "joey"
|
||||||
"/home/joey/lib/backup" $ intercalate "&&" $
|
"/home/joey/lib/backup" gitriddancecmd
|
||||||
[ "mkdir -p github"
|
|
||||||
, "cd github"
|
|
||||||
, ". $HOME/.github-keys"
|
|
||||||
, "github-backup joeyh"
|
|
||||||
]
|
|
||||||
, Cron.niceJob "gitriddance" "30 4 * * *" "joey"
|
|
||||||
"/home/joey/lib/backup" $ intercalate "&&" $
|
|
||||||
[ "cd github"
|
|
||||||
, ". $HOME/.github-keys"
|
|
||||||
] ++ map gitriddance githubMirrors
|
|
||||||
]
|
|
||||||
where
|
where
|
||||||
|
backupcmd = intercalate "&&" $
|
||||||
|
[ "mkdir -p github"
|
||||||
|
, "cd github"
|
||||||
|
, ". $HOME/.github-keys"
|
||||||
|
, "github-backup joeyh"
|
||||||
|
]
|
||||||
|
gitriddancecmd = intercalate "&&" $
|
||||||
|
[ "cd github"
|
||||||
|
, ". $HOME/.github-keys"
|
||||||
|
] ++ map gitriddance githubMirrors
|
||||||
gitriddance (r, msg) = "(cd " ++ r ++ " && gitriddance " ++ shellEscape msg ++ ")"
|
gitriddance (r, msg) = "(cd " ++ r ++ " && gitriddance " ++ shellEscape msg ++ ")"
|
||||||
|
|
||||||
|
githubKeys :: Property HasInfo
|
||||||
|
githubKeys =
|
||||||
|
let f = "/home/joey/.github-keys"
|
||||||
|
in File.hasPrivContent f anyContext
|
||||||
|
`onChange` File.ownerGroup f "joey" "joey"
|
||||||
|
|
||||||
|
|
||||||
-- these repos are only mirrored on github, I don't want
|
-- these repos are only mirrored on github, I don't want
|
||||||
-- all the proprietary features
|
-- all the proprietary features
|
||||||
githubMirrors :: [(String, String)]
|
githubMirrors :: [(String, String)]
|
||||||
|
@ -380,12 +384,12 @@ githubMirrors =
|
||||||
where
|
where
|
||||||
plzuseurl u = "please submit changes to " ++ u ++ " instead of using github pull requests"
|
plzuseurl u = "please submit changes to " ++ u ++ " instead of using github pull requests"
|
||||||
|
|
||||||
rsyncNetBackup :: [Host] -> Property
|
rsyncNetBackup :: [Host] -> Property NoInfo
|
||||||
rsyncNetBackup hosts = Cron.niceJob "rsync.net copied in daily" "30 5 * * *"
|
rsyncNetBackup hosts = Cron.niceJob "rsync.net copied in daily" "30 5 * * *"
|
||||||
"joey" "/home/joey/lib/backup" "mkdir -p rsync.net && rsync --delete -az 2318@usw-s002.rsync.net: rsync.net"
|
"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" "joey"
|
`requires` Ssh.knownHost hosts "usw-s002.rsync.net" "joey"
|
||||||
|
|
||||||
backupsBackedupTo :: [Host] -> HostName -> FilePath -> Property
|
backupsBackedupTo :: [Host] -> HostName -> FilePath -> Property NoInfo
|
||||||
backupsBackedupTo hosts desthost destdir = Cron.niceJob desc
|
backupsBackedupTo hosts desthost destdir = Cron.niceJob desc
|
||||||
"1 1 * * 3" "joey" "/" cmd
|
"1 1 * * 3" "joey" "/" cmd
|
||||||
`requires` Ssh.knownHost hosts desthost "joey"
|
`requires` Ssh.knownHost hosts desthost "joey"
|
||||||
|
@ -393,7 +397,7 @@ backupsBackedupTo hosts desthost destdir = Cron.niceJob desc
|
||||||
desc = "backups copied to " ++ desthost ++ " weekly"
|
desc = "backups copied to " ++ desthost ++ " weekly"
|
||||||
cmd = "rsync -az --delete /home/joey/lib/backup " ++ desthost ++ ":" ++ destdir
|
cmd = "rsync -az --delete /home/joey/lib/backup " ++ desthost ++ ":" ++ destdir
|
||||||
|
|
||||||
obnamRepos :: [String] -> Property
|
obnamRepos :: [String] -> Property NoInfo
|
||||||
obnamRepos rs = propertyList ("obnam repos for " ++ unwords rs)
|
obnamRepos rs = propertyList ("obnam repos for " ++ unwords rs)
|
||||||
(mkbase : map mkrepo rs)
|
(mkbase : map mkrepo rs)
|
||||||
where
|
where
|
||||||
|
@ -403,23 +407,22 @@ obnamRepos rs = propertyList ("obnam repos for " ++ unwords rs)
|
||||||
mkdir d = File.dirExists d
|
mkdir d = File.dirExists d
|
||||||
`before` File.ownerGroup d "joey" "joey"
|
`before` File.ownerGroup d "joey" "joey"
|
||||||
|
|
||||||
podcatcher :: Property
|
podcatcher :: Property NoInfo
|
||||||
podcatcher = Cron.niceJob "podcatcher run hourly" "55 * * * *"
|
podcatcher = Cron.niceJob "podcatcher run hourly" "55 * * * *"
|
||||||
"joey" "/home/joey/lib/sound/podcasts"
|
"joey" "/home/joey/lib/sound/podcasts"
|
||||||
"xargs git-annex importfeed -c annex.genmetadata=true < feeds; mr --quiet update"
|
"xargs git-annex importfeed -c annex.genmetadata=true < feeds; mr --quiet update"
|
||||||
`requires` Apt.installed ["git-annex", "myrepos"]
|
`requires` Apt.installed ["git-annex", "myrepos"]
|
||||||
|
|
||||||
kiteMailServer :: Property
|
kiteMailServer :: Property HasInfo
|
||||||
kiteMailServer = propertyList "kitenet.net mail server"
|
kiteMailServer = propertyList "kitenet.net mail server" $ props
|
||||||
[ Postfix.installed
|
& Postfix.installed
|
||||||
, Apt.installed ["postfix-pcre"]
|
& Apt.installed ["postfix-pcre"]
|
||||||
, Apt.serviceInstalledRunning "postgrey"
|
& Apt.serviceInstalledRunning "postgrey"
|
||||||
|
|
||||||
, Apt.serviceInstalledRunning "spamassassin"
|
& Apt.serviceInstalledRunning "spamassassin"
|
||||||
, "/etc/default/spamassassin" `File.containsLines`
|
& "/etc/default/spamassassin" `File.containsLines`
|
||||||
[ "# Propellor deployed"
|
[ "# Propellor deployed"
|
||||||
, "ENABLED=1"
|
, "ENABLED=1"
|
||||||
, "CRON=1"
|
|
||||||
, "OPTIONS=\"--create-prefs --max-children 5 --helper-home-dir\""
|
, "OPTIONS=\"--create-prefs --max-children 5 --helper-home-dir\""
|
||||||
, "CRON=1"
|
, "CRON=1"
|
||||||
, "NICE=\"--nicelevel 15\""
|
, "NICE=\"--nicelevel 15\""
|
||||||
|
@ -427,15 +430,15 @@ kiteMailServer = propertyList "kitenet.net mail server"
|
||||||
`describe` "spamd enabled"
|
`describe` "spamd enabled"
|
||||||
`requires` Apt.serviceInstalledRunning "cron"
|
`requires` Apt.serviceInstalledRunning "cron"
|
||||||
|
|
||||||
, Apt.serviceInstalledRunning "spamass-milter"
|
& Apt.serviceInstalledRunning "spamass-milter"
|
||||||
-- Add -m to prevent modifying messages Subject or body.
|
-- Add -m to prevent modifying messages Subject or body.
|
||||||
, "/etc/default/spamass-milter" `File.containsLine`
|
& "/etc/default/spamass-milter" `File.containsLine`
|
||||||
"OPTIONS=\"-m -u spamass-milter -i 127.0.0.1\""
|
"OPTIONS=\"-m -u spamass-milter -i 127.0.0.1\""
|
||||||
`onChange` Service.restarted "spamass-milter"
|
`onChange` Service.restarted "spamass-milter"
|
||||||
`describe` "spamass-milter configured"
|
`describe` "spamass-milter configured"
|
||||||
|
|
||||||
, Apt.serviceInstalledRunning "amavisd-milter"
|
& Apt.serviceInstalledRunning "amavisd-milter"
|
||||||
, "/etc/default/amavisd-milter" `File.containsLines`
|
& "/etc/default/amavisd-milter" `File.containsLines`
|
||||||
[ "# Propellor deployed"
|
[ "# Propellor deployed"
|
||||||
, "MILTERSOCKET=/var/spool/postfix/amavis/amavis.sock"
|
, "MILTERSOCKET=/var/spool/postfix/amavis/amavis.sock"
|
||||||
, "MILTERSOCKETOWNER=\"postfix:postfix\""
|
, "MILTERSOCKETOWNER=\"postfix:postfix\""
|
||||||
|
@ -443,12 +446,12 @@ kiteMailServer = propertyList "kitenet.net mail server"
|
||||||
]
|
]
|
||||||
`onChange` Service.restarted "amavisd-milter"
|
`onChange` Service.restarted "amavisd-milter"
|
||||||
`describe` "amavisd-milter configured for postfix"
|
`describe` "amavisd-milter configured for postfix"
|
||||||
, Apt.serviceInstalledRunning "clamav-freshclam"
|
& Apt.serviceInstalledRunning "clamav-freshclam"
|
||||||
|
|
||||||
, dkimInstalled
|
& dkimInstalled
|
||||||
|
|
||||||
, Apt.installed ["maildrop"]
|
& Apt.installed ["maildrop"]
|
||||||
, "/etc/maildroprc" `File.hasContent`
|
& "/etc/maildroprc" `File.hasContent`
|
||||||
[ "# Global maildrop filter file (deployed with propellor)"
|
[ "# Global maildrop filter file (deployed with propellor)"
|
||||||
, "DEFAULT=\"$HOME/Maildir\""
|
, "DEFAULT=\"$HOME/Maildir\""
|
||||||
, "MAILBOX=\"$DEFAULT/.\""
|
, "MAILBOX=\"$DEFAULT/.\""
|
||||||
|
@ -462,19 +465,19 @@ kiteMailServer = propertyList "kitenet.net mail server"
|
||||||
]
|
]
|
||||||
`describe` "maildrop configured"
|
`describe` "maildrop configured"
|
||||||
|
|
||||||
, "/etc/aliases" `File.hasPrivContentExposed` ctx
|
& "/etc/aliases" `File.hasPrivContentExposed` ctx
|
||||||
`onChange` Postfix.newaliases
|
`onChange` Postfix.newaliases
|
||||||
, hasJoeyCAChain
|
& hasJoeyCAChain
|
||||||
, hasPostfixCert ctx
|
& hasPostfixCert ctx
|
||||||
|
|
||||||
, "/etc/postfix/mydomain" `File.containsLines`
|
& "/etc/postfix/mydomain" `File.containsLines`
|
||||||
[ "/.*\\.kitenet\\.net/\tOK"
|
[ "/.*\\.kitenet\\.net/\tOK"
|
||||||
, "/ikiwiki\\.info/\tOK"
|
, "/ikiwiki\\.info/\tOK"
|
||||||
, "/joeyh\\.name/\tOK"
|
, "/joeyh\\.name/\tOK"
|
||||||
]
|
]
|
||||||
`onChange` Postfix.reloaded
|
`onChange` Postfix.reloaded
|
||||||
`describe` "postfix mydomain file configured"
|
`describe` "postfix mydomain file configured"
|
||||||
, "/etc/postfix/obscure_client_relay.pcre" `File.hasContent`
|
& "/etc/postfix/obscure_client_relay.pcre" `File.hasContent`
|
||||||
-- Remove received lines for mails relayed from trusted
|
-- Remove received lines for mails relayed from trusted
|
||||||
-- clients. These can be a privacy violation, or trigger
|
-- clients. These can be a privacy violation, or trigger
|
||||||
-- spam filters.
|
-- spam filters.
|
||||||
|
@ -486,16 +489,16 @@ kiteMailServer = propertyList "kitenet.net mail server"
|
||||||
]
|
]
|
||||||
`onChange` Postfix.reloaded
|
`onChange` Postfix.reloaded
|
||||||
`describe` "postfix obscure_client_relay file configured"
|
`describe` "postfix obscure_client_relay file configured"
|
||||||
, Postfix.mappedFile "/etc/postfix/virtual"
|
& Postfix.mappedFile "/etc/postfix/virtual"
|
||||||
(flip File.containsLines
|
(flip File.containsLines
|
||||||
[ "# *@joeyh.name to joey"
|
[ "# *@joeyh.name to joey"
|
||||||
, "@joeyh.name\tjoey"
|
, "@joeyh.name\tjoey"
|
||||||
]
|
]
|
||||||
) `describe` "postfix virtual file configured"
|
) `describe` "postfix virtual file configured"
|
||||||
`onChange` Postfix.reloaded
|
`onChange` Postfix.reloaded
|
||||||
, Postfix.mappedFile "/etc/postfix/relay_clientcerts" $
|
& Postfix.mappedFile "/etc/postfix/relay_clientcerts"
|
||||||
flip File.hasPrivContentExposed ctx
|
(flip File.hasPrivContentExposed ctx)
|
||||||
, Postfix.mainCfFile `File.containsLines`
|
& Postfix.mainCfFile `File.containsLines`
|
||||||
[ "myhostname = kitenet.net"
|
[ "myhostname = kitenet.net"
|
||||||
, "mydomain = $myhostname"
|
, "mydomain = $myhostname"
|
||||||
, "append_dot_mydomain = no"
|
, "append_dot_mydomain = no"
|
||||||
|
@ -544,24 +547,24 @@ kiteMailServer = propertyList "kitenet.net mail server"
|
||||||
`onChange` Postfix.reloaded
|
`onChange` Postfix.reloaded
|
||||||
`describe` "postfix configured"
|
`describe` "postfix configured"
|
||||||
|
|
||||||
, Apt.serviceInstalledRunning "dovecot-imapd"
|
& Apt.serviceInstalledRunning "dovecot-imapd"
|
||||||
, Apt.serviceInstalledRunning "dovecot-pop3d"
|
& Apt.serviceInstalledRunning "dovecot-pop3d"
|
||||||
, "/etc/dovecot/conf.d/10-mail.conf" `File.containsLine`
|
& "/etc/dovecot/conf.d/10-mail.conf" `File.containsLine`
|
||||||
"mail_location = maildir:~/Maildir"
|
"mail_location = maildir:~/Maildir"
|
||||||
`onChange` Service.reloaded "dovecot"
|
`onChange` Service.reloaded "dovecot"
|
||||||
`describe` "dovecot mail.conf"
|
`describe` "dovecot mail.conf"
|
||||||
, "/etc/dovecot/conf.d/10-auth.conf" `File.containsLine`
|
& "/etc/dovecot/conf.d/10-auth.conf" `File.containsLine`
|
||||||
"!include auth-passwdfile.conf.ext"
|
"!include auth-passwdfile.conf.ext"
|
||||||
`onChange` Service.restarted "dovecot"
|
`onChange` Service.restarted "dovecot"
|
||||||
`describe` "dovecot auth.conf"
|
`describe` "dovecot auth.conf"
|
||||||
, File.hasPrivContent dovecotusers ctx
|
& File.hasPrivContent dovecotusers ctx
|
||||||
`onChange` (dovecotusers `File.mode`
|
`onChange` (dovecotusers `File.mode`
|
||||||
combineModes [ownerReadMode, groupReadMode])
|
combineModes [ownerReadMode, groupReadMode])
|
||||||
, File.ownerGroup dovecotusers "root" "dovecot"
|
& File.ownerGroup dovecotusers "root" "dovecot"
|
||||||
|
|
||||||
, Apt.installed ["mutt", "bsd-mailx", "alpine"]
|
& Apt.installed ["mutt", "bsd-mailx", "alpine"]
|
||||||
|
|
||||||
, pinescript `File.hasContent`
|
& pinescript `File.hasContent`
|
||||||
[ "#!/bin/sh"
|
[ "#!/bin/sh"
|
||||||
, "# deployed with propellor"
|
, "# deployed with propellor"
|
||||||
, "set -e"
|
, "set -e"
|
||||||
|
@ -575,14 +578,13 @@ kiteMailServer = propertyList "kitenet.net mail server"
|
||||||
`onChange` (pinescript `File.mode`
|
`onChange` (pinescript `File.mode`
|
||||||
combineModes (readModes ++ executeModes))
|
combineModes (readModes ++ executeModes))
|
||||||
`describe` "pine wrapper script"
|
`describe` "pine wrapper script"
|
||||||
, "/etc/pine.conf" `File.hasContent`
|
& "/etc/pine.conf" `File.hasContent`
|
||||||
[ "# deployed with propellor"
|
[ "# deployed with propellor"
|
||||||
, "inbox-path={localhost/novalidate-cert/NoRsh}inbox"
|
, "inbox-path={localhost/novalidate-cert/NoRsh}inbox"
|
||||||
]
|
]
|
||||||
`describe` "pine configured to use local imap server"
|
`describe` "pine configured to use local imap server"
|
||||||
|
|
||||||
, Apt.serviceInstalledRunning "mailman"
|
& Apt.serviceInstalledRunning "mailman"
|
||||||
]
|
|
||||||
where
|
where
|
||||||
ctx = Context "kitenet.net"
|
ctx = Context "kitenet.net"
|
||||||
pinescript = "/usr/local/bin/pine"
|
pinescript = "/usr/local/bin/pine"
|
||||||
|
@ -590,7 +592,7 @@ kiteMailServer = propertyList "kitenet.net mail server"
|
||||||
|
|
||||||
-- Configures postfix to relay outgoing mail to kitenet.net, with
|
-- Configures postfix to relay outgoing mail to kitenet.net, with
|
||||||
-- verification via tls cert.
|
-- verification via tls cert.
|
||||||
postfixClientRelay :: Context -> Property
|
postfixClientRelay :: Context -> Property HasInfo
|
||||||
postfixClientRelay ctx = Postfix.mainCfFile `File.containsLines`
|
postfixClientRelay ctx = Postfix.mainCfFile `File.containsLines`
|
||||||
[ "relayhost = kitenet.net"
|
[ "relayhost = kitenet.net"
|
||||||
, "smtp_tls_CAfile = /etc/ssl/certs/joeyca.pem"
|
, "smtp_tls_CAfile = /etc/ssl/certs/joeyca.pem"
|
||||||
|
@ -606,7 +608,7 @@ postfixClientRelay ctx = Postfix.mainCfFile `File.containsLines`
|
||||||
`requires` hasPostfixCert ctx
|
`requires` hasPostfixCert ctx
|
||||||
|
|
||||||
-- Configures postfix to have the dkim milter, and no other milters.
|
-- Configures postfix to have the dkim milter, and no other milters.
|
||||||
dkimMilter :: Property
|
dkimMilter :: Property HasInfo
|
||||||
dkimMilter = Postfix.mainCfFile `File.containsLines`
|
dkimMilter = Postfix.mainCfFile `File.containsLines`
|
||||||
[ "smtpd_milters = inet:localhost:8891"
|
[ "smtpd_milters = inet:localhost:8891"
|
||||||
, "non_smtpd_milters = inet:localhost:8891"
|
, "non_smtpd_milters = inet:localhost:8891"
|
||||||
|
@ -619,22 +621,22 @@ dkimMilter = Postfix.mainCfFile `File.containsLines`
|
||||||
|
|
||||||
-- This does not configure postfix to use the dkim milter,
|
-- This does not configure postfix to use the dkim milter,
|
||||||
-- nor does it set up domainkey DNS.
|
-- nor does it set up domainkey DNS.
|
||||||
dkimInstalled :: Property
|
dkimInstalled :: Property HasInfo
|
||||||
dkimInstalled = propertyList "opendkim installed"
|
dkimInstalled = go `onChange` Service.restarted "opendkim"
|
||||||
[ Apt.serviceInstalledRunning "opendkim"
|
where
|
||||||
, File.dirExists "/etc/mail"
|
go = propertyList "opendkim installed" $ props
|
||||||
, File.hasPrivContent "/etc/mail/dkim.key" (Context "kitenet.net")
|
& Apt.serviceInstalledRunning "opendkim"
|
||||||
, File.ownerGroup "/etc/mail/dkim.key" "opendkim" "opendkim"
|
& File.dirExists "/etc/mail"
|
||||||
, "/etc/default/opendkim" `File.containsLine`
|
& File.hasPrivContent "/etc/mail/dkim.key" (Context "kitenet.net")
|
||||||
"SOCKET=\"inet:8891@localhost\""
|
& File.ownerGroup "/etc/mail/dkim.key" "opendkim" "opendkim"
|
||||||
, "/etc/opendkim.conf" `File.containsLines`
|
& "/etc/default/opendkim" `File.containsLine`
|
||||||
[ "KeyFile /etc/mail/dkim.key"
|
"SOCKET=\"inet:8891@localhost\""
|
||||||
, "SubDomains yes"
|
& "/etc/opendkim.conf" `File.containsLines`
|
||||||
, "Domain *"
|
[ "KeyFile /etc/mail/dkim.key"
|
||||||
, "Selector mail"
|
, "SubDomains yes"
|
||||||
]
|
, "Domain *"
|
||||||
]
|
, "Selector mail"
|
||||||
`onChange` Service.restarted "opendkim"
|
]
|
||||||
|
|
||||||
-- This is the dkim public key, corresponding with /etc/mail/dkim.key
|
-- This is the dkim public key, corresponding with /etc/mail/dkim.key
|
||||||
-- This value can be included in a domain's additional records to make
|
-- This value can be included in a domain's additional records to make
|
||||||
|
@ -642,37 +644,36 @@ dkimInstalled = propertyList "opendkim installed"
|
||||||
domainKey :: (BindDomain, Record)
|
domainKey :: (BindDomain, Record)
|
||||||
domainKey = (RelDomain "mail._domainkey", TXT "v=DKIM1; k=rsa; t=y; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCc+/rfzNdt5DseBBmfB3C6sVM7FgVvf4h1FeCfyfwPpVcmPdW6M2I+NtJsbRkNbEICxiP6QY2UM0uoo9TmPqLgiCCG2vtuiG6XMsS0Y/gGwqKM7ntg/7vT1Go9vcquOFFuLa5PnzpVf8hB9+PMFdS4NPTvWL2c5xxshl/RJzICnQIDAQAB")
|
domainKey = (RelDomain "mail._domainkey", TXT "v=DKIM1; k=rsa; t=y; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCc+/rfzNdt5DseBBmfB3C6sVM7FgVvf4h1FeCfyfwPpVcmPdW6M2I+NtJsbRkNbEICxiP6QY2UM0uoo9TmPqLgiCCG2vtuiG6XMsS0Y/gGwqKM7ntg/7vT1Go9vcquOFFuLa5PnzpVf8hB9+PMFdS4NPTvWL2c5xxshl/RJzICnQIDAQAB")
|
||||||
|
|
||||||
hasJoeyCAChain :: Property
|
hasJoeyCAChain :: Property HasInfo
|
||||||
hasJoeyCAChain = "/etc/ssl/certs/joeyca.pem" `File.hasPrivContentExposed`
|
hasJoeyCAChain = "/etc/ssl/certs/joeyca.pem" `File.hasPrivContentExposed`
|
||||||
Context "joeyca.pem"
|
Context "joeyca.pem"
|
||||||
|
|
||||||
hasPostfixCert :: Context -> Property
|
hasPostfixCert :: Context -> Property HasInfo
|
||||||
hasPostfixCert ctx = combineProperties "postfix tls cert installed"
|
hasPostfixCert ctx = combineProperties "postfix tls cert installed"
|
||||||
[ "/etc/ssl/certs/postfix.pem" `File.hasPrivContentExposed` ctx
|
[ "/etc/ssl/certs/postfix.pem" `File.hasPrivContentExposed` ctx
|
||||||
, "/etc/ssl/private/postfix.pem" `File.hasPrivContent` ctx
|
, "/etc/ssl/private/postfix.pem" `File.hasPrivContent` ctx
|
||||||
]
|
]
|
||||||
|
|
||||||
kitenetHttps :: Property
|
kitenetHttps :: Property HasInfo
|
||||||
kitenetHttps = propertyList "kitenet.net https certs"
|
kitenetHttps = propertyList "kitenet.net https certs" $ props
|
||||||
[ File.hasPrivContent "/etc/ssl/certs/web.pem" ctx
|
& File.hasPrivContent "/etc/ssl/certs/web.pem" ctx
|
||||||
, File.hasPrivContent "/etc/ssl/private/web.pem" ctx
|
& File.hasPrivContent "/etc/ssl/private/web.pem" ctx
|
||||||
, File.hasPrivContent "/etc/ssl/certs/startssl.pem" ctx
|
& File.hasPrivContent "/etc/ssl/certs/startssl.pem" ctx
|
||||||
, toProp $ Apache.modEnabled "ssl"
|
& Apache.modEnabled "ssl"
|
||||||
]
|
|
||||||
where
|
where
|
||||||
ctx = Context "kitenet.net"
|
ctx = Context "kitenet.net"
|
||||||
|
|
||||||
-- Legacy static web sites and redirections from kitenet.net to newer
|
-- Legacy static web sites and redirections from kitenet.net to newer
|
||||||
-- sites.
|
-- sites.
|
||||||
legacyWebSites :: Property
|
legacyWebSites :: Property HasInfo
|
||||||
legacyWebSites = propertyList "legacy web sites"
|
legacyWebSites = propertyList "legacy web sites" $ props
|
||||||
[ Apt.serviceInstalledRunning "apache2"
|
& Apt.serviceInstalledRunning "apache2"
|
||||||
, toProp $ Apache.modEnabled "rewrite"
|
& Apache.modEnabled "rewrite"
|
||||||
, toProp $ Apache.modEnabled "cgi"
|
& Apache.modEnabled "cgi"
|
||||||
, toProp $ Apache.modEnabled "speling"
|
& Apache.modEnabled "speling"
|
||||||
, userDirHtml
|
& userDirHtml
|
||||||
, kitenetHttps
|
& kitenetHttps
|
||||||
, toProp $ Apache.siteEnabled "kitenet.net" $ apachecfg "kitenet.net" True
|
& apacheSite "kitenet.net" True
|
||||||
-- /var/www is empty
|
-- /var/www is empty
|
||||||
[ "DocumentRoot /var/www"
|
[ "DocumentRoot /var/www"
|
||||||
, "<Directory /var/www>"
|
, "<Directory /var/www>"
|
||||||
|
@ -759,8 +760,8 @@ legacyWebSites = propertyList "legacy web sites"
|
||||||
, "rewriterule /~kyle/family/wiki/(.*).rss http://macleawiki.branchable.com/$1/index.rss [L]"
|
, "rewriterule /~kyle/family/wiki/(.*).rss http://macleawiki.branchable.com/$1/index.rss [L]"
|
||||||
, "rewriterule /~kyle/family/wiki(.*) http://macleawiki.branchable.com$1 [L]"
|
, "rewriterule /~kyle/family/wiki(.*) http://macleawiki.branchable.com$1 [L]"
|
||||||
]
|
]
|
||||||
, alias "anna.kitenet.net"
|
& alias "anna.kitenet.net"
|
||||||
, toProp $ Apache.siteEnabled "anna.kitenet.net" $ apachecfg "anna.kitenet.net" False
|
& apacheSite "anna.kitenet.net" False
|
||||||
[ "DocumentRoot /home/anna/html"
|
[ "DocumentRoot /home/anna/html"
|
||||||
, "<Directory /home/anna/html/>"
|
, "<Directory /home/anna/html/>"
|
||||||
, " Options Indexes ExecCGI"
|
, " Options Indexes ExecCGI"
|
||||||
|
@ -768,9 +769,9 @@ legacyWebSites = propertyList "legacy web sites"
|
||||||
, Apache.allowAll
|
, Apache.allowAll
|
||||||
, "</Directory>"
|
, "</Directory>"
|
||||||
]
|
]
|
||||||
, alias "sows-ear.kitenet.net"
|
& alias "sows-ear.kitenet.net"
|
||||||
, alias "www.sows-ear.kitenet.net"
|
& alias "www.sows-ear.kitenet.net"
|
||||||
, toProp $ Apache.siteEnabled "sows-ear.kitenet.net" $ apachecfg "sows-ear.kitenet.net" False
|
& apacheSite "sows-ear.kitenet.net" False
|
||||||
[ "ServerAlias www.sows-ear.kitenet.net"
|
[ "ServerAlias www.sows-ear.kitenet.net"
|
||||||
, "DocumentRoot /srv/web/sows-ear.kitenet.net"
|
, "DocumentRoot /srv/web/sows-ear.kitenet.net"
|
||||||
, "<Directory /srv/web/sows-ear.kitenet.net>"
|
, "<Directory /srv/web/sows-ear.kitenet.net>"
|
||||||
|
@ -779,9 +780,9 @@ legacyWebSites = propertyList "legacy web sites"
|
||||||
, Apache.allowAll
|
, Apache.allowAll
|
||||||
, "</Directory>"
|
, "</Directory>"
|
||||||
]
|
]
|
||||||
, alias "wortroot.kitenet.net"
|
& alias "wortroot.kitenet.net"
|
||||||
, alias "www.wortroot.kitenet.net"
|
& alias "www.wortroot.kitenet.net"
|
||||||
, toProp $ Apache.siteEnabled "wortroot.kitenet.net" $ apachecfg "wortroot.kitenet.net" False
|
& apacheSite "wortroot.kitenet.net" False
|
||||||
[ "ServerAlias www.wortroot.kitenet.net"
|
[ "ServerAlias www.wortroot.kitenet.net"
|
||||||
, "DocumentRoot /srv/web/wortroot.kitenet.net"
|
, "DocumentRoot /srv/web/wortroot.kitenet.net"
|
||||||
, "<Directory /srv/web/wortroot.kitenet.net>"
|
, "<Directory /srv/web/wortroot.kitenet.net>"
|
||||||
|
@ -790,8 +791,8 @@ legacyWebSites = propertyList "legacy web sites"
|
||||||
, Apache.allowAll
|
, Apache.allowAll
|
||||||
, "</Directory>"
|
, "</Directory>"
|
||||||
]
|
]
|
||||||
, alias "creeksidepress.com"
|
& alias "creeksidepress.com"
|
||||||
, toProp $ Apache.siteEnabled "creeksidepress.com" $ apachecfg "creeksidepress.com" False
|
& apacheSite "creeksidepress.com" False
|
||||||
[ "ServerAlias www.creeksidepress.com"
|
[ "ServerAlias www.creeksidepress.com"
|
||||||
, "DocumentRoot /srv/web/www.creeksidepress.com"
|
, "DocumentRoot /srv/web/www.creeksidepress.com"
|
||||||
, "<Directory /srv/web/www.creeksidepress.com>"
|
, "<Directory /srv/web/www.creeksidepress.com>"
|
||||||
|
@ -800,8 +801,8 @@ legacyWebSites = propertyList "legacy web sites"
|
||||||
, Apache.allowAll
|
, Apache.allowAll
|
||||||
, "</Directory>"
|
, "</Directory>"
|
||||||
]
|
]
|
||||||
, alias "joey.kitenet.net"
|
& alias "joey.kitenet.net"
|
||||||
, toProp $ Apache.siteEnabled "joey.kitenet.net" $ apachecfg "joey.kitenet.net" False
|
& apacheSite "joey.kitenet.net" False
|
||||||
[ "DocumentRoot /var/www"
|
[ "DocumentRoot /var/www"
|
||||||
, "<Directory /var/www/>"
|
, "<Directory /var/www/>"
|
||||||
, " Options Indexes ExecCGI"
|
, " Options Indexes ExecCGI"
|
||||||
|
@ -821,12 +822,12 @@ legacyWebSites = propertyList "legacy web sites"
|
||||||
, "# Redirect all to joeyh.name."
|
, "# Redirect all to joeyh.name."
|
||||||
, "rewriterule (.*) http://joeyh.name$1 [r]"
|
, "rewriterule (.*) http://joeyh.name$1 [r]"
|
||||||
]
|
]
|
||||||
]
|
|
||||||
|
|
||||||
userDirHtml :: Property
|
userDirHtml :: Property HasInfo
|
||||||
userDirHtml = File.fileProperty "apache userdir is html" (map munge) conf
|
userDirHtml = File.fileProperty "apache userdir is html" (map munge) conf
|
||||||
`onChange` Apache.reloaded
|
`onChange` Apache.reloaded
|
||||||
`requires` (toProp $ Apache.modEnabled "userdir")
|
`requires` (toProp $ Apache.modEnabled "userdir")
|
||||||
where
|
where
|
||||||
munge = replace "public_html" "html"
|
munge = replace "public_html" "html"
|
||||||
conf = "/etc/apache2/mods-available/userdir.conf"
|
conf = "/etc/apache2/mods-available/userdir.conf"
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
module Propellor.Property.Ssh (
|
module Propellor.Property.Ssh (
|
||||||
|
PubKeyText,
|
||||||
setSshdConfig,
|
setSshdConfig,
|
||||||
permitRootLogin,
|
permitRootLogin,
|
||||||
passwordAuthentication,
|
passwordAuthentication,
|
||||||
|
@ -35,7 +36,7 @@ sshBool False = "no"
|
||||||
sshdConfig :: FilePath
|
sshdConfig :: FilePath
|
||||||
sshdConfig = "/etc/ssh/sshd_config"
|
sshdConfig = "/etc/ssh/sshd_config"
|
||||||
|
|
||||||
setSshdConfig :: String -> Bool -> Property
|
setSshdConfig :: String -> Bool -> Property NoInfo
|
||||||
setSshdConfig setting allowed = combineProperties "sshd config"
|
setSshdConfig setting allowed = combineProperties "sshd config"
|
||||||
[ sshdConfig `File.lacksLine` (sshline $ not allowed)
|
[ sshdConfig `File.lacksLine` (sshline $ not allowed)
|
||||||
, sshdConfig `File.containsLine` (sshline allowed)
|
, sshdConfig `File.containsLine` (sshline allowed)
|
||||||
|
@ -45,10 +46,10 @@ setSshdConfig setting allowed = combineProperties "sshd config"
|
||||||
where
|
where
|
||||||
sshline v = setting ++ " " ++ sshBool v
|
sshline v = setting ++ " " ++ sshBool v
|
||||||
|
|
||||||
permitRootLogin :: Bool -> Property
|
permitRootLogin :: Bool -> Property NoInfo
|
||||||
permitRootLogin = setSshdConfig "PermitRootLogin"
|
permitRootLogin = setSshdConfig "PermitRootLogin"
|
||||||
|
|
||||||
passwordAuthentication :: Bool -> Property
|
passwordAuthentication :: Bool -> Property NoInfo
|
||||||
passwordAuthentication = setSshdConfig "PasswordAuthentication"
|
passwordAuthentication = setSshdConfig "PasswordAuthentication"
|
||||||
|
|
||||||
dotDir :: UserName -> IO FilePath
|
dotDir :: UserName -> IO FilePath
|
||||||
|
@ -66,13 +67,13 @@ hasAuthorizedKeys = go <=< dotFile "authorized_keys"
|
||||||
where
|
where
|
||||||
go f = not . null <$> catchDefaultIO "" (readFile f)
|
go f = not . null <$> catchDefaultIO "" (readFile f)
|
||||||
|
|
||||||
restarted :: Property
|
restarted :: Property NoInfo
|
||||||
restarted = Service.restarted "ssh"
|
restarted = Service.restarted "ssh"
|
||||||
|
|
||||||
-- | Blows away existing host keys and make new ones.
|
-- | Blows away existing host keys and make new ones.
|
||||||
-- Useful for systems installed from an image that might reuse host keys.
|
-- Useful for systems installed from an image that might reuse host keys.
|
||||||
-- A flag file is used to only ever do this once.
|
-- A flag file is used to only ever do this once.
|
||||||
randomHostKeys :: Property
|
randomHostKeys :: Property NoInfo
|
||||||
randomHostKeys = flagFile prop "/etc/ssh/.unique_host_keys"
|
randomHostKeys = flagFile prop "/etc/ssh/.unique_host_keys"
|
||||||
`onChange` restarted
|
`onChange` restarted
|
||||||
where
|
where
|
||||||
|
@ -89,7 +90,7 @@ randomHostKeys = flagFile prop "/etc/ssh/.unique_host_keys"
|
||||||
-- The corresponding private keys come from the privdata.
|
-- The corresponding private keys come from the privdata.
|
||||||
--
|
--
|
||||||
-- Any host keysthat are not in the list are removed from the host.
|
-- Any host keysthat are not in the list are removed from the host.
|
||||||
hostKeys :: IsContext c => c -> [(SshKeyType, PubKeyText)] -> Property
|
hostKeys :: IsContext c => c -> [(SshKeyType, PubKeyText)] -> Property HasInfo
|
||||||
hostKeys ctx l = propertyList desc $ catMaybes $
|
hostKeys ctx l = propertyList desc $ catMaybes $
|
||||||
map (\(t, pub) -> Just $ hostKey ctx t pub) l ++ [cleanup]
|
map (\(t, pub) -> Just $ hostKey ctx t pub) l ++ [cleanup]
|
||||||
where
|
where
|
||||||
|
@ -100,19 +101,20 @@ hostKeys ctx l = propertyList desc $ catMaybes $
|
||||||
removestale b = map (File.notPresent . flip keyFile b) staletypes
|
removestale b = map (File.notPresent . flip keyFile b) staletypes
|
||||||
cleanup
|
cleanup
|
||||||
| null staletypes || null l = Nothing
|
| null staletypes || null l = Nothing
|
||||||
| otherwise = Just $ property ("any other ssh host keys removed " ++ typelist staletypes) $
|
| otherwise = Just $ toProp $
|
||||||
ensureProperty $
|
property ("any other ssh host keys removed " ++ typelist staletypes) $
|
||||||
combineProperties desc (removestale True ++ removestale False)
|
ensureProperty $
|
||||||
`onChange` restarted
|
combineProperties desc (removestale True ++ removestale False)
|
||||||
|
`onChange` restarted
|
||||||
|
|
||||||
-- | Installs a single ssh host key of a particular type.
|
-- | Installs a single ssh host key of a particular type.
|
||||||
--
|
--
|
||||||
-- The public key is provided to this function;
|
-- The public key is provided to this function;
|
||||||
-- the private key comes from the privdata;
|
-- the private key comes from the privdata;
|
||||||
hostKey :: IsContext c => c -> SshKeyType -> PubKeyText -> Property
|
hostKey :: IsContext c => c -> SshKeyType -> PubKeyText -> Property HasInfo
|
||||||
hostKey context keytype pub = combineProperties desc
|
hostKey context keytype pub = combineProperties desc
|
||||||
[ pubKey keytype pub
|
[ pubKey keytype pub
|
||||||
, property desc $ install writeFile True pub
|
, toProp $ property desc $ install writeFile True pub
|
||||||
, withPrivData (keysrc "" (SshPrivKey keytype "")) context $ \getkey ->
|
, withPrivData (keysrc "" (SshPrivKey keytype "")) context $ \getkey ->
|
||||||
property desc $ getkey $ install writeFileProtected False
|
property desc $ getkey $ install writeFileProtected False
|
||||||
]
|
]
|
||||||
|
@ -136,7 +138,7 @@ keyFile keytype ispub = "/etc/ssh/ssh_host_" ++ fromKeyType keytype ++ "_key" ++
|
||||||
-- | Indicates the host key that is used by a Host, but does not actually
|
-- | 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;
|
-- configure the host to use it. Normally this does not need to be used;
|
||||||
-- use 'hostKey' instead.
|
-- use 'hostKey' instead.
|
||||||
pubKey :: SshKeyType -> PubKeyText -> Property
|
pubKey :: SshKeyType -> PubKeyText -> Property HasInfo
|
||||||
pubKey t k = pureInfoProperty ("ssh pubkey known") $
|
pubKey t k = pureInfoProperty ("ssh pubkey known") $
|
||||||
mempty { _sshPubKey = M.singleton t k }
|
mempty { _sshPubKey = M.singleton t k }
|
||||||
|
|
||||||
|
@ -145,7 +147,7 @@ getPubKey = asks (_sshPubKey . hostInfo)
|
||||||
|
|
||||||
-- | Sets up a user with a ssh private key and public key pair from the
|
-- | Sets up a user with a ssh private key and public key pair from the
|
||||||
-- PrivData.
|
-- PrivData.
|
||||||
keyImported :: IsContext c => SshKeyType -> UserName -> c -> Property
|
keyImported :: IsContext c => SshKeyType -> UserName -> c -> Property HasInfo
|
||||||
keyImported keytype user context = combineProperties desc
|
keyImported keytype user context = combineProperties desc
|
||||||
[ installkey (SshPubKey keytype user) (install writeFile ".pub")
|
[ installkey (SshPubKey keytype user) (install writeFile ".pub")
|
||||||
, installkey (SshPrivKey keytype user) (install writeFileProtected "")
|
, installkey (SshPrivKey keytype user) (install writeFileProtected "")
|
||||||
|
@ -178,7 +180,7 @@ fromKeyType SshEd25519 = "ed25519"
|
||||||
|
|
||||||
-- | Puts some host's ssh public key(s), as set using 'pubKey',
|
-- | Puts some host's ssh public key(s), as set using 'pubKey',
|
||||||
-- into the known_hosts file for a user.
|
-- into the known_hosts file for a user.
|
||||||
knownHost :: [Host] -> HostName -> UserName -> Property
|
knownHost :: [Host] -> HostName -> UserName -> Property NoInfo
|
||||||
knownHost hosts hn user = property desc $
|
knownHost hosts hn user = property desc $
|
||||||
go =<< fromHost hosts hn getPubKey
|
go =<< fromHost hosts hn getPubKey
|
||||||
where
|
where
|
||||||
|
@ -198,7 +200,7 @@ knownHost hosts hn user = property desc $
|
||||||
-- | Makes a user have authorized_keys from the PrivData
|
-- | Makes a user have authorized_keys from the PrivData
|
||||||
--
|
--
|
||||||
-- This removes any other lines from the file.
|
-- This removes any other lines from the file.
|
||||||
authorizedKeys :: IsContext c => UserName -> c -> Property
|
authorizedKeys :: IsContext c => UserName -> c -> Property HasInfo
|
||||||
authorizedKeys user context = withPrivData (SshAuthorizedKeys user) context $ \get ->
|
authorizedKeys user context = withPrivData (SshAuthorizedKeys user) context $ \get ->
|
||||||
property (user ++ " has authorized_keys") $ get $ \v -> do
|
property (user ++ " has authorized_keys") $ get $ \v -> do
|
||||||
f <- liftIO $ dotFile "authorized_keys" user
|
f <- liftIO $ dotFile "authorized_keys" user
|
||||||
|
@ -212,7 +214,7 @@ authorizedKeys user context = withPrivData (SshAuthorizedKeys user) context $ \g
|
||||||
|
|
||||||
-- | Ensures that a user's authorized_keys contains a line.
|
-- | Ensures that a user's authorized_keys contains a line.
|
||||||
-- Any other lines in the file are preserved as-is.
|
-- Any other lines in the file are preserved as-is.
|
||||||
authorizedKey :: UserName -> String -> Property
|
authorizedKey :: UserName -> String -> Property NoInfo
|
||||||
authorizedKey user l = property (user ++ " has autorized_keys line " ++ l) $ do
|
authorizedKey user l = property (user ++ " has autorized_keys line " ++ l) $ do
|
||||||
f <- liftIO $ dotFile "authorized_keys" user
|
f <- liftIO $ dotFile "authorized_keys" user
|
||||||
ensureProperty $
|
ensureProperty $
|
||||||
|
@ -225,7 +227,7 @@ authorizedKey user l = property (user ++ " has autorized_keys line " ++ l) $ do
|
||||||
--
|
--
|
||||||
-- Revert to prevent it listening on a particular port.
|
-- Revert to prevent it listening on a particular port.
|
||||||
listenPort :: Int -> RevertableProperty
|
listenPort :: Int -> RevertableProperty
|
||||||
listenPort port = RevertableProperty enable disable
|
listenPort port = enable <!> disable
|
||||||
where
|
where
|
||||||
portline = "Port " ++ show port
|
portline = "Port " ++ show port
|
||||||
enable = sshdConfig `File.containsLine` portline
|
enable = sshdConfig `File.containsLine` portline
|
||||||
|
|
|
@ -9,7 +9,7 @@ import Propellor.Property.User
|
||||||
|
|
||||||
-- | Allows a user to sudo. If the user has a password, sudo is configured
|
-- | 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.
|
-- to require it. If not, NOPASSWORD is enabled for the user.
|
||||||
enabledFor :: UserName -> Property
|
enabledFor :: UserName -> Property NoInfo
|
||||||
enabledFor user = property desc go `requires` Apt.installed ["sudo"]
|
enabledFor user = property desc go `requires` Apt.installed ["sudo"]
|
||||||
where
|
where
|
||||||
go = do
|
go = do
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
module Propellor.Property.Systemd (
|
module Propellor.Property.Systemd (
|
||||||
module Propellor.Property.Systemd.Core,
|
module Propellor.Property.Systemd.Core,
|
||||||
|
ServiceName,
|
||||||
|
MachineName,
|
||||||
started,
|
started,
|
||||||
stopped,
|
stopped,
|
||||||
enabled,
|
enabled,
|
||||||
disabled,
|
disabled,
|
||||||
|
restarted,
|
||||||
persistentJournal,
|
persistentJournal,
|
||||||
|
Option,
|
||||||
|
configured,
|
||||||
|
journaldConfigured,
|
||||||
daemonReloaded,
|
daemonReloaded,
|
||||||
Container,
|
Container,
|
||||||
container,
|
container,
|
||||||
|
@ -33,33 +39,38 @@ type MachineName = String
|
||||||
data Container = Container MachineName Chroot.Chroot Host
|
data Container = Container MachineName Chroot.Chroot Host
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
instance Hostlike Container where
|
instance PropAccum Container where
|
||||||
(Container n c h) & p = Container n c (h & p)
|
(Container n c h) & p = Container n c (h & p)
|
||||||
(Container n c h) &^ p = Container n c (h &^ p)
|
(Container n c h) &^ p = Container n c (h &^ p)
|
||||||
getHost (Container _ _ h) = h
|
getProperties (Container _ _ h) = hostProperties h
|
||||||
|
|
||||||
-- | Starts a systemd service.
|
-- | Starts a systemd service.
|
||||||
started :: ServiceName -> Property
|
started :: ServiceName -> Property NoInfo
|
||||||
started n = trivial $ cmdProperty "systemctl" ["start", n]
|
started n = trivial $ cmdProperty "systemctl" ["start", n]
|
||||||
`describe` ("service " ++ n ++ " started")
|
`describe` ("service " ++ n ++ " started")
|
||||||
|
|
||||||
-- | Stops a systemd service.
|
-- | Stops a systemd service.
|
||||||
stopped :: ServiceName -> Property
|
stopped :: ServiceName -> Property NoInfo
|
||||||
stopped n = trivial $ cmdProperty "systemctl" ["stop", n]
|
stopped n = trivial $ cmdProperty "systemctl" ["stop", n]
|
||||||
`describe` ("service " ++ n ++ " stopped")
|
`describe` ("service " ++ n ++ " stopped")
|
||||||
|
|
||||||
-- | Enables a systemd service.
|
-- | Enables a systemd service.
|
||||||
enabled :: ServiceName -> Property
|
enabled :: ServiceName -> Property NoInfo
|
||||||
enabled n = trivial $ cmdProperty "systemctl" ["enable", n]
|
enabled n = trivial $ cmdProperty "systemctl" ["enable", n]
|
||||||
`describe` ("service " ++ n ++ " enabled")
|
`describe` ("service " ++ n ++ " enabled")
|
||||||
|
|
||||||
-- | Disables a systemd service.
|
-- | Disables a systemd service.
|
||||||
disabled :: ServiceName -> Property
|
disabled :: ServiceName -> Property NoInfo
|
||||||
disabled n = trivial $ cmdProperty "systemctl" ["disable", n]
|
disabled n = trivial $ cmdProperty "systemctl" ["disable", n]
|
||||||
`describe` ("service " ++ n ++ " disabled")
|
`describe` ("service " ++ n ++ " disabled")
|
||||||
|
|
||||||
|
-- | Restarts a systemd service.
|
||||||
|
restarted :: ServiceName -> Property NoInfo
|
||||||
|
restarted n = trivial $ cmdProperty "systemctl" ["restart", n]
|
||||||
|
`describe` ("service " ++ n ++ " restarted")
|
||||||
|
|
||||||
-- | Enables persistent storage of the journal.
|
-- | Enables persistent storage of the journal.
|
||||||
persistentJournal :: Property
|
persistentJournal :: Property NoInfo
|
||||||
persistentJournal = check (not <$> doesDirectoryExist dir) $
|
persistentJournal = check (not <$> doesDirectoryExist dir) $
|
||||||
combineProperties "persistent systemd journal"
|
combineProperties "persistent systemd journal"
|
||||||
[ cmdProperty "install" ["-d", "-g", "systemd-journal", dir]
|
[ cmdProperty "install" ["-d", "-g", "systemd-journal", dir]
|
||||||
|
@ -70,8 +81,35 @@ persistentJournal = check (not <$> doesDirectoryExist dir) $
|
||||||
where
|
where
|
||||||
dir = "/var/log/journal"
|
dir = "/var/log/journal"
|
||||||
|
|
||||||
|
type Option = String
|
||||||
|
|
||||||
|
-- | Ensures that an option is configured in one of systemd's config files.
|
||||||
|
-- Does not ensure that the relevant daemon notices the change immediately.
|
||||||
|
--
|
||||||
|
-- This assumes that there is only one [Header] per file, which is
|
||||||
|
-- currently the case. 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
|
||||||
|
]
|
||||||
|
where
|
||||||
|
setting = option ++ "="
|
||||||
|
line = setting ++ value
|
||||||
|
desc = cfgfile ++ " " ++ line
|
||||||
|
removeother l
|
||||||
|
| setting `isPrefixOf` l = Nothing
|
||||||
|
| otherwise = Just l
|
||||||
|
|
||||||
|
-- | Configures journald, restarting it so the changes take effect.
|
||||||
|
journaldConfigured :: Option -> String -> Property NoInfo
|
||||||
|
journaldConfigured option value =
|
||||||
|
configured "/etc/systemd/journald.conf" option value
|
||||||
|
`onChange` restarted "systemd-journald"
|
||||||
|
|
||||||
-- | Causes systemd to reload its configuration files.
|
-- | Causes systemd to reload its configuration files.
|
||||||
daemonReloaded :: Property
|
daemonReloaded :: Property NoInfo
|
||||||
daemonReloaded = trivial $ cmdProperty "systemctl" ["daemon-reload"]
|
daemonReloaded = trivial $ cmdProperty "systemctl" ["daemon-reload"]
|
||||||
|
|
||||||
-- | Defines a container with a given machine name.
|
-- | Defines a container with a given machine name.
|
||||||
|
@ -105,17 +143,12 @@ container name mkchroot = Container name c h
|
||||||
-- and deletes the chroot and all its contents.
|
-- and deletes the chroot and all its contents.
|
||||||
nspawned :: Container -> RevertableProperty
|
nspawned :: Container -> RevertableProperty
|
||||||
nspawned c@(Container name (Chroot.Chroot loc system builderconf _) h) =
|
nspawned c@(Container name (Chroot.Chroot loc system builderconf _) h) =
|
||||||
RevertableProperty setup teardown
|
p `describe` ("nspawned " ++ name)
|
||||||
where
|
where
|
||||||
setup = combineProperties ("nspawned " ++ name) $
|
p = enterScript c
|
||||||
map toProp steps ++ [containerprovisioned]
|
`before` chrootprovisioned
|
||||||
teardown = combineProperties ("not nspawned " ++ name) $
|
`before` nspawnService c (_chrootCfg $ _chrootinfo $ hostInfo h)
|
||||||
map (toProp . revert) (reverse steps)
|
`before` containerprovisioned
|
||||||
steps =
|
|
||||||
[ enterScript c
|
|
||||||
, chrootprovisioned
|
|
||||||
, nspawnService c (_chrootCfg $ _chrootinfo $ hostInfo h)
|
|
||||||
]
|
|
||||||
|
|
||||||
-- Chroot provisioning is run in systemd-only mode,
|
-- Chroot provisioning is run in systemd-only mode,
|
||||||
-- which sets up the chroot and ensures systemd and dbus are
|
-- which sets up the chroot and ensures systemd and dbus are
|
||||||
|
@ -125,15 +158,17 @@ nspawned c@(Container name (Chroot.Chroot loc system builderconf _) h) =
|
||||||
|
|
||||||
-- Use nsenter to enter container and and run propellor to
|
-- Use nsenter to enter container and and run propellor to
|
||||||
-- finish provisioning.
|
-- finish provisioning.
|
||||||
containerprovisioned = Chroot.propellChroot chroot
|
containerprovisioned =
|
||||||
(enterContainerProcess c) False
|
Chroot.propellChroot chroot (enterContainerProcess c) False
|
||||||
|
<!>
|
||||||
|
doNothing
|
||||||
|
|
||||||
chroot = Chroot.Chroot loc system builderconf h
|
chroot = Chroot.Chroot loc system builderconf h
|
||||||
|
|
||||||
-- | Sets up the service file for the container, and then starts
|
-- | Sets up the service file for the container, and then starts
|
||||||
-- it running.
|
-- it running.
|
||||||
nspawnService :: Container -> ChrootCfg -> RevertableProperty
|
nspawnService :: Container -> ChrootCfg -> RevertableProperty
|
||||||
nspawnService (Container name _ _) cfg = RevertableProperty setup teardown
|
nspawnService (Container name _ _) cfg = setup <!> teardown
|
||||||
where
|
where
|
||||||
service = nspawnServiceName name
|
service = nspawnServiceName name
|
||||||
servicefile = "/etc/systemd/system/multi-user.target.wants" </> service
|
servicefile = "/etc/systemd/system/multi-user.target.wants" </> service
|
||||||
|
@ -177,7 +212,7 @@ nspawnServiceParams (SystemdNspawnCfg ps) =
|
||||||
-- This uses nsenter to enter the container, by looking up the pid of the
|
-- This uses nsenter to enter the container, by looking up the pid of the
|
||||||
-- container's init process and using its namespace.
|
-- container's init process and using its namespace.
|
||||||
enterScript :: Container -> RevertableProperty
|
enterScript :: Container -> RevertableProperty
|
||||||
enterScript c@(Container name _ _) = RevertableProperty setup teardown
|
enterScript c@(Container name _ _) = setup <!> teardown
|
||||||
where
|
where
|
||||||
setup = combineProperties ("generated " ++ enterScriptFile c)
|
setup = combineProperties ("generated " ++ enterScriptFile c)
|
||||||
[ scriptfile `File.hasContent`
|
[ scriptfile `File.hasContent`
|
||||||
|
|
|
@ -6,5 +6,5 @@ import qualified Propellor.Property.Apt as Apt
|
||||||
-- dbus is only a Recommends of systemd, but is needed for communication
|
-- 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
|
-- from the systemd inside a container to the one outside, so make sure it
|
||||||
-- gets installed.
|
-- gets installed.
|
||||||
installed :: Property
|
installed :: Property NoInfo
|
||||||
installed = Apt.installed ["systemd", "dbus"]
|
installed = Apt.installed ["systemd", "dbus"]
|
||||||
|
|
|
@ -10,7 +10,7 @@ import System.Posix.Files
|
||||||
|
|
||||||
type HiddenServiceName = String
|
type HiddenServiceName = String
|
||||||
|
|
||||||
isBridge :: Property
|
isBridge :: Property NoInfo
|
||||||
isBridge = setup `requires` Apt.installed ["tor"]
|
isBridge = setup `requires` Apt.installed ["tor"]
|
||||||
`describe` "tor bridge"
|
`describe` "tor bridge"
|
||||||
where
|
where
|
||||||
|
@ -21,7 +21,7 @@ isBridge = setup `requires` Apt.installed ["tor"]
|
||||||
, "Exitpolicy reject *:*"
|
, "Exitpolicy reject *:*"
|
||||||
] `onChange` restarted
|
] `onChange` restarted
|
||||||
|
|
||||||
hiddenServiceAvailable :: HiddenServiceName -> Int -> Property
|
hiddenServiceAvailable :: HiddenServiceName -> Int -> Property NoInfo
|
||||||
hiddenServiceAvailable hn port = hiddenServiceHostName prop
|
hiddenServiceAvailable hn port = hiddenServiceHostName prop
|
||||||
where
|
where
|
||||||
prop = mainConfig `File.containsLines`
|
prop = mainConfig `File.containsLines`
|
||||||
|
@ -30,13 +30,13 @@ hiddenServiceAvailable hn port = hiddenServiceHostName prop
|
||||||
]
|
]
|
||||||
`describe` "hidden service available"
|
`describe` "hidden service available"
|
||||||
`onChange` Service.reloaded "tor"
|
`onChange` Service.reloaded "tor"
|
||||||
hiddenServiceHostName p = adjustProperty p $ \satisfy -> do
|
hiddenServiceHostName p = adjustPropertySatisfy p $ \satisfy -> do
|
||||||
r <- satisfy
|
r <- satisfy
|
||||||
h <- liftIO $ readFile (varLib </> hn </> "hostname")
|
h <- liftIO $ readFile (varLib </> hn </> "hostname")
|
||||||
warningMessage $ unwords ["hidden service hostname:", h]
|
warningMessage $ unwords ["hidden service hostname:", h]
|
||||||
return r
|
return r
|
||||||
|
|
||||||
hiddenService :: HiddenServiceName -> Int -> Property
|
hiddenService :: HiddenServiceName -> Int -> Property NoInfo
|
||||||
hiddenService hn port = mainConfig `File.containsLines`
|
hiddenService hn port = mainConfig `File.containsLines`
|
||||||
[ unwords ["HiddenServiceDir", varLib </> hn]
|
[ unwords ["HiddenServiceDir", varLib </> hn]
|
||||||
, unwords ["HiddenServicePort", show port, "127.0.0.1:" ++ show port]
|
, unwords ["HiddenServicePort", show port, "127.0.0.1:" ++ show port]
|
||||||
|
@ -44,7 +44,7 @@ hiddenService hn port = mainConfig `File.containsLines`
|
||||||
`describe` unwords ["hidden service available:", hn, show port]
|
`describe` unwords ["hidden service available:", hn, show port]
|
||||||
`onChange` restarted
|
`onChange` restarted
|
||||||
|
|
||||||
hiddenServiceData :: IsContext c => HiddenServiceName -> c -> Property
|
hiddenServiceData :: IsContext c => HiddenServiceName -> c -> Property HasInfo
|
||||||
hiddenServiceData hn context = combineProperties desc
|
hiddenServiceData hn context = combineProperties desc
|
||||||
[ installonion "hostname"
|
[ installonion "hostname"
|
||||||
, installonion "private_key"
|
, installonion "private_key"
|
||||||
|
@ -66,7 +66,7 @@ hiddenServiceData hn context = combineProperties desc
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
restarted :: Property
|
restarted :: Property NoInfo
|
||||||
restarted = Service.restarted "tor"
|
restarted = Service.restarted "tor"
|
||||||
|
|
||||||
mainConfig :: FilePath
|
mainConfig :: FilePath
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Propellor
|
||||||
|
|
||||||
data Eep = YesReallyDeleteHome
|
data Eep = YesReallyDeleteHome
|
||||||
|
|
||||||
accountFor :: UserName -> Property
|
accountFor :: UserName -> Property NoInfo
|
||||||
accountFor user = check (isNothing <$> catchMaybeIO (homedir user)) $ cmdProperty "adduser"
|
accountFor user = check (isNothing <$> catchMaybeIO (homedir user)) $ cmdProperty "adduser"
|
||||||
[ "--disabled-password"
|
[ "--disabled-password"
|
||||||
, "--gecos", ""
|
, "--gecos", ""
|
||||||
|
@ -15,7 +15,7 @@ accountFor user = check (isNothing <$> catchMaybeIO (homedir user)) $ cmdPropert
|
||||||
`describe` ("account for " ++ user)
|
`describe` ("account for " ++ user)
|
||||||
|
|
||||||
-- | Removes user home directory!! Use with caution.
|
-- | Removes user home directory!! Use with caution.
|
||||||
nuked :: UserName -> Eep -> Property
|
nuked :: UserName -> Eep -> Property NoInfo
|
||||||
nuked user _ = check (isJust <$> catchMaybeIO (homedir user)) $ cmdProperty "userdel"
|
nuked user _ = check (isJust <$> catchMaybeIO (homedir user)) $ cmdProperty "userdel"
|
||||||
[ "-r"
|
[ "-r"
|
||||||
, user
|
, user
|
||||||
|
@ -24,13 +24,13 @@ nuked user _ = check (isJust <$> catchMaybeIO (homedir user)) $ cmdProperty "use
|
||||||
|
|
||||||
-- | Only ensures that the user has some password set. It may or may
|
-- | Only ensures that the user has some password set. It may or may
|
||||||
-- not be a password from the PrivData.
|
-- not be a password from the PrivData.
|
||||||
hasSomePassword :: UserName -> Property
|
hasSomePassword :: UserName -> Property HasInfo
|
||||||
hasSomePassword user = hasSomePassword' user hostContext
|
hasSomePassword user = hasSomePassword' user hostContext
|
||||||
|
|
||||||
-- | While hasSomePassword uses the name of the host as context,
|
-- | While hasSomePassword uses the name of the host as context,
|
||||||
-- this allows specifying a different context. This is useful when
|
-- this allows specifying a different context. This is useful when
|
||||||
-- you want to use the same password on multiple hosts, for example.
|
-- you want to use the same password on multiple hosts, for example.
|
||||||
hasSomePassword' :: IsContext c => UserName -> c -> Property
|
hasSomePassword' :: IsContext c => UserName -> c -> Property HasInfo
|
||||||
hasSomePassword' user context = check ((/= HasPassword) <$> getPasswordStatus user) $
|
hasSomePassword' user context = check ((/= HasPassword) <$> getPasswordStatus user) $
|
||||||
hasPassword' user context
|
hasPassword' user context
|
||||||
|
|
||||||
|
@ -40,10 +40,10 @@ hasSomePassword' user context = check ((/= HasPassword) <$> getPasswordStatus us
|
||||||
-- A user's password can be stored in the PrivData in either of two forms;
|
-- 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
|
-- the full cleartext <Password> or a <CryptPassword> hash. The latter
|
||||||
-- is obviously more secure.
|
-- is obviously more secure.
|
||||||
hasPassword :: UserName -> Property
|
hasPassword :: UserName -> Property HasInfo
|
||||||
hasPassword user = hasPassword' user hostContext
|
hasPassword user = hasPassword' user hostContext
|
||||||
|
|
||||||
hasPassword' :: IsContext c => UserName -> c -> Property
|
hasPassword' :: IsContext c => UserName -> c -> Property HasInfo
|
||||||
hasPassword' user context = go `requires` shadowConfig True
|
hasPassword' user context = go `requires` shadowConfig True
|
||||||
where
|
where
|
||||||
go = withSomePrivData srcs context $
|
go = withSomePrivData srcs context $
|
||||||
|
@ -66,7 +66,7 @@ setPassword getpassword = getpassword $ go
|
||||||
hPutStrLn h $ user ++ ":" ++ v
|
hPutStrLn h $ user ++ ":" ++ v
|
||||||
hClose h
|
hClose h
|
||||||
|
|
||||||
lockedPassword :: UserName -> Property
|
lockedPassword :: UserName -> Property NoInfo
|
||||||
lockedPassword user = check (not <$> isLockedPassword user) $ cmdProperty "passwd"
|
lockedPassword user = check (not <$> isLockedPassword user) $ cmdProperty "passwd"
|
||||||
[ "--lock"
|
[ "--lock"
|
||||||
, user
|
, user
|
||||||
|
@ -90,7 +90,7 @@ isLockedPassword user = (== LockedPassword) <$> getPasswordStatus user
|
||||||
homedir :: UserName -> IO FilePath
|
homedir :: UserName -> IO FilePath
|
||||||
homedir user = homeDirectory <$> getUserEntryForName user
|
homedir user = homeDirectory <$> getUserEntryForName user
|
||||||
|
|
||||||
hasGroup :: UserName -> GroupName -> Property
|
hasGroup :: UserName -> GroupName -> Property NoInfo
|
||||||
hasGroup user group' = check test $ cmdProperty "adduser"
|
hasGroup user group' = check test $ cmdProperty "adduser"
|
||||||
[ user
|
[ user
|
||||||
, group'
|
, group'
|
||||||
|
@ -100,7 +100,7 @@ hasGroup user group' = check test $ cmdProperty "adduser"
|
||||||
test = not . elem group' . words <$> readProcess "groups" [user]
|
test = not . elem group' . words <$> readProcess "groups" [user]
|
||||||
|
|
||||||
-- | Controls whether shadow passwords are enabled or not.
|
-- | Controls whether shadow passwords are enabled or not.
|
||||||
shadowConfig :: Bool -> Property
|
shadowConfig :: Bool -> Property NoInfo
|
||||||
shadowConfig True = check (not <$> shadowExists) $
|
shadowConfig True = check (not <$> shadowExists) $
|
||||||
cmdProperty "shadowconfig" ["on"]
|
cmdProperty "shadowconfig" ["on"]
|
||||||
`describe` "shadow passwords enabled"
|
`describe` "shadow passwords enabled"
|
||||||
|
|
|
@ -24,6 +24,7 @@ import Propellor.PrivData.Paths
|
||||||
import Propellor.Git
|
import Propellor.Git
|
||||||
import Propellor.Ssh
|
import Propellor.Ssh
|
||||||
import Propellor.Gpg
|
import Propellor.Gpg
|
||||||
|
import Propellor.Types.CmdLine
|
||||||
import qualified Propellor.Shim as Shim
|
import qualified Propellor.Shim as Shim
|
||||||
import Utility.FileMode
|
import Utility.FileMode
|
||||||
import Utility.SafeCommand
|
import Utility.SafeCommand
|
||||||
|
|
|
@ -1,44 +1,48 @@
|
||||||
{-# LANGUAGE PackageImports #-}
|
{-# LANGUAGE PackageImports #-}
|
||||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||||
|
{-# LANGUAGE GADTs #-}
|
||||||
|
{-# LANGUAGE EmptyDataDecls #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
|
||||||
module Propellor.Types
|
module Propellor.Types
|
||||||
( Host(..)
|
( Host(..)
|
||||||
, Info(..)
|
|
||||||
, getInfo
|
|
||||||
, Propellor(..)
|
|
||||||
, Property(..)
|
|
||||||
, RevertableProperty(..)
|
|
||||||
, IsProp
|
|
||||||
, describe
|
|
||||||
, toProp
|
|
||||||
, requires
|
|
||||||
, Desc
|
, Desc
|
||||||
, Result(..)
|
, Property
|
||||||
, ToResult(..)
|
, HasInfo
|
||||||
, ActionResult(..)
|
, NoInfo
|
||||||
, CmdLine(..)
|
, CInfo
|
||||||
, PrivDataField(..)
|
, infoProperty
|
||||||
, PrivData
|
, simpleProperty
|
||||||
, Context(..)
|
, adjustPropertySatisfy
|
||||||
, anyContext
|
, propertyInfo
|
||||||
, SshKeyType(..)
|
, propertyDesc
|
||||||
, Val(..)
|
, propertyChildren
|
||||||
, fromVal
|
, RevertableProperty(..)
|
||||||
, RunLog
|
, (<!>)
|
||||||
|
, IsProp(..)
|
||||||
|
, Combines(..)
|
||||||
|
, CombinedType
|
||||||
|
, before
|
||||||
|
, combineWith
|
||||||
|
, Info(..)
|
||||||
|
, Propellor(..)
|
||||||
, EndAction(..)
|
, EndAction(..)
|
||||||
, module Propellor.Types.OS
|
, module Propellor.Types.OS
|
||||||
, module Propellor.Types.Dns
|
, module Propellor.Types.Dns
|
||||||
|
, module Propellor.Types.Result
|
||||||
|
, propertySatisfy
|
||||||
|
, ignoreInfo
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Monoid
|
import Data.Monoid
|
||||||
import Control.Applicative
|
import Control.Applicative
|
||||||
import System.Console.ANSI
|
|
||||||
import System.Posix.Types
|
|
||||||
import "mtl" Control.Monad.RWS.Strict
|
import "mtl" Control.Monad.RWS.Strict
|
||||||
import "MonadCatchIO-transformers" Control.Monad.CatchIO
|
import "MonadCatchIO-transformers" Control.Monad.CatchIO
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
import qualified Propellor.Types.Dns as Dns
|
|
||||||
|
|
||||||
import Propellor.Types.OS
|
import Propellor.Types.OS
|
||||||
import Propellor.Types.Chroot
|
import Propellor.Types.Chroot
|
||||||
|
@ -46,137 +50,228 @@ import Propellor.Types.Dns
|
||||||
import Propellor.Types.Docker
|
import Propellor.Types.Docker
|
||||||
import Propellor.Types.PrivData
|
import Propellor.Types.PrivData
|
||||||
import Propellor.Types.Empty
|
import Propellor.Types.Empty
|
||||||
|
import Propellor.Types.Val
|
||||||
|
import Propellor.Types.Result
|
||||||
|
import qualified Propellor.Types.Dns as Dns
|
||||||
|
|
||||||
-- | Everything Propellor knows about a system: Its hostname,
|
-- | Everything Propellor knows about a system: Its hostname,
|
||||||
-- properties and other info.
|
-- properties and their collected info.
|
||||||
data Host = Host
|
data Host = Host
|
||||||
{ hostName :: HostName
|
{ hostName :: HostName
|
||||||
, hostProperties :: [Property]
|
, hostProperties :: [Property HasInfo]
|
||||||
, hostInfo :: Info
|
, hostInfo :: Info
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
-- | Propellor's monad provides read-only access to info about the host
|
-- | Propellor's monad provides read-only access to info about the host
|
||||||
-- it's running on, and a writer to accumulate logs about the run.
|
-- it's running on, and a writer to accumulate EndActions.
|
||||||
newtype Propellor p = Propellor { runWithHost :: RWST Host RunLog () IO p }
|
newtype Propellor p = Propellor { runWithHost :: RWST Host [EndAction] () IO p }
|
||||||
deriving
|
deriving
|
||||||
( Monad
|
( Monad
|
||||||
, Functor
|
, Functor
|
||||||
, Applicative
|
, Applicative
|
||||||
, MonadReader Host
|
, MonadReader Host
|
||||||
, MonadWriter RunLog
|
, MonadWriter [EndAction]
|
||||||
, MonadIO
|
, MonadIO
|
||||||
, MonadCatchIO
|
, MonadCatchIO
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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
|
-- | The core data type of Propellor, this represents a property
|
||||||
-- that the system should have, and an action to ensure it has the
|
-- that the system should have, and an action to ensure it has the
|
||||||
-- property.
|
-- property.
|
||||||
data Property = Property
|
data Property i where
|
||||||
{ propertyDesc :: Desc
|
IProperty :: Desc -> Propellor Result -> Info -> [Property HasInfo] -> Property HasInfo
|
||||||
, propertySatisfy :: Propellor Result
|
SProperty :: Desc -> Propellor Result -> [Property NoInfo] -> Property NoInfo
|
||||||
-- ^ must be idempotent; may run repeatedly
|
|
||||||
, propertyInfo :: Info
|
|
||||||
-- ^ a property can add info to the host.
|
|
||||||
}
|
|
||||||
|
|
||||||
instance Show Property where
|
-- | Indicates that a Property has associated Info.
|
||||||
show p = "property " ++ show (propertyDesc p)
|
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
|
||||||
|
|
||||||
|
-- | Makes a version of a Proprty without its Info.
|
||||||
|
-- Use with caution!
|
||||||
|
ignoreInfo :: Property i -> Property NoInfo
|
||||||
|
ignoreInfo = toSProperty
|
||||||
|
|
||||||
|
-- | 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
|
||||||
|
|
||||||
|
instance Show (Property i) where
|
||||||
|
show p = "property " ++ show (propertyDesc p)
|
||||||
|
|
||||||
|
-- | 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
|
||||||
|
|
||||||
|
-- | 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
|
||||||
|
|
||||||
-- | A property that can be reverted.
|
-- | A property that can be reverted.
|
||||||
data RevertableProperty = RevertableProperty Property Property
|
data RevertableProperty = RevertableProperty (Property HasInfo) (Property HasInfo)
|
||||||
|
|
||||||
|
-- | Makes a revertable property; the first Property is run
|
||||||
|
-- normally and the second is run when it's reverted.
|
||||||
|
(<!>) :: Property i1 -> Property i2 -> RevertableProperty
|
||||||
|
p1 <!> p2 = RevertableProperty (toIProperty p1) (toIProperty p2)
|
||||||
|
|
||||||
class IsProp p where
|
class IsProp p where
|
||||||
-- | Sets description.
|
-- | Sets description.
|
||||||
describe :: p -> Desc -> p
|
describe :: p -> Desc -> p
|
||||||
toProp :: p -> Property
|
toProp :: p -> Property HasInfo
|
||||||
-- | Indicates that the first property can only be satisfied
|
getDesc :: p -> Desc
|
||||||
-- once the second one is.
|
-- | Gets the info of the property, combined with all info
|
||||||
requires :: p -> Property -> p
|
-- of all children properties.
|
||||||
getInfo :: p -> Info
|
getInfoRecursive :: p -> Info
|
||||||
|
|
||||||
instance IsProp Property where
|
instance IsProp (Property HasInfo) where
|
||||||
describe p d = p { propertyDesc = d }
|
describe (IProperty _ a i cs) d = IProperty d a i cs
|
||||||
toProp p = p
|
toProp = id
|
||||||
getInfo = propertyInfo
|
getDesc = propertyDesc
|
||||||
x `requires` y = Property (propertyDesc x) satisfy info
|
getInfoRecursive (IProperty _ _ i cs) =
|
||||||
where
|
i <> mconcat (map getInfoRecursive cs)
|
||||||
info = getInfo y <> getInfo x
|
instance IsProp (Property NoInfo) where
|
||||||
satisfy = do
|
describe (SProperty _ a cs) d = SProperty d a cs
|
||||||
r <- propertySatisfy y
|
toProp = toIProperty
|
||||||
case r of
|
getDesc = propertyDesc
|
||||||
FailedChange -> return FailedChange
|
getInfoRecursive _ = mempty
|
||||||
_ -> propertySatisfy x
|
|
||||||
|
|
||||||
|
|
||||||
instance IsProp RevertableProperty where
|
instance IsProp RevertableProperty where
|
||||||
-- | Sets the description of both sides.
|
-- | Sets the description of both sides.
|
||||||
describe (RevertableProperty p1 p2) d =
|
describe (RevertableProperty p1 p2) d =
|
||||||
RevertableProperty (describe p1 d) (describe p2 ("not " ++ d))
|
RevertableProperty (describe p1 d) (describe p2 ("not " ++ d))
|
||||||
|
getDesc (RevertableProperty p1 _) = getDesc p1
|
||||||
toProp (RevertableProperty p1 _) = p1
|
toProp (RevertableProperty p1 _) = p1
|
||||||
(RevertableProperty p1 p2) `requires` y =
|
|
||||||
RevertableProperty (p1 `requires` y) p2
|
|
||||||
-- | Return the Info of the currently active side.
|
-- | Return the Info of the currently active side.
|
||||||
getInfo (RevertableProperty p1 _p2) = getInfo p1
|
getInfoRecursive (RevertableProperty p1 _p2) = getInfoRecursive p1
|
||||||
|
|
||||||
type Desc = String
|
-- | Type level calculation of the type that results from combining two types
|
||||||
|
-- with `requires`.
|
||||||
|
type family CombinedType x y
|
||||||
|
type instance CombinedType (Property x) (Property y) = Property (CInfo x y)
|
||||||
|
type instance CombinedType RevertableProperty (Property NoInfo) = RevertableProperty
|
||||||
|
type instance CombinedType RevertableProperty (Property HasInfo) = RevertableProperty
|
||||||
|
type instance CombinedType RevertableProperty RevertableProperty = RevertableProperty
|
||||||
|
|
||||||
data Result = NoChange | MadeChange | FailedChange
|
class Combines x y where
|
||||||
deriving (Read, Show, Eq)
|
-- | Indicates that the first property depends on the second,
|
||||||
|
-- so before the first is ensured, the second will be ensured.
|
||||||
|
requires :: x -> y -> CombinedType x y
|
||||||
|
|
||||||
|
-- | Combines together two properties, resulting in one property
|
||||||
|
-- that ensures the first, and if the first succeeds, ensures the second.
|
||||||
|
-- The property uses the description of the first property.
|
||||||
|
before :: (IsProp x, Combines y x, IsProp (CombinedType y x)) => x -> y -> CombinedType y x
|
||||||
|
before x y = (y `requires` x) `describe` (getDesc x)
|
||||||
|
|
||||||
instance Monoid Result where
|
-- | Combines together two properties, yielding a property that
|
||||||
mempty = NoChange
|
-- has the description and info of the first, and that has the second
|
||||||
|
-- property as a child. The two actions to satisfy the properties
|
||||||
|
-- are passed to a function that can combine them in arbitrary ways.
|
||||||
|
combineWith
|
||||||
|
:: (Combines (Property x) (Property y))
|
||||||
|
=> (Propellor Result -> Propellor Result -> Propellor Result)
|
||||||
|
-> Property x
|
||||||
|
-> Property y
|
||||||
|
-> CombinedType (Property x) (Property y)
|
||||||
|
combineWith f x y = adjustPropertySatisfy (x `requires` y) $ \_ ->
|
||||||
|
f (propertySatisfy $ toSProperty x) (propertySatisfy $ toSProperty y)
|
||||||
|
|
||||||
mappend FailedChange _ = FailedChange
|
instance Combines (Property HasInfo) (Property HasInfo) where
|
||||||
mappend _ FailedChange = FailedChange
|
requires (IProperty d1 a1 i1 cs1) y@(IProperty _d2 a2 _i2 _cs2) =
|
||||||
mappend MadeChange _ = MadeChange
|
IProperty d1 (a2 <> a1) i1 (y : cs1)
|
||||||
mappend _ MadeChange = MadeChange
|
|
||||||
mappend NoChange NoChange = NoChange
|
|
||||||
|
|
||||||
class ToResult t where
|
instance Combines (Property HasInfo) (Property NoInfo) where
|
||||||
toResult :: t -> Result
|
requires (IProperty d1 a1 i1 cs1) y@(SProperty _d2 a2 _cs2) =
|
||||||
|
IProperty d1 (a2 <> a1) i1 (toIProperty y : cs1)
|
||||||
|
|
||||||
instance ToResult Bool where
|
instance Combines (Property NoInfo) (Property HasInfo) where
|
||||||
toResult False = FailedChange
|
requires (SProperty d1 a1 cs1) y@(IProperty _d2 a2 _i2 _cs2) =
|
||||||
toResult True = MadeChange
|
IProperty d1 (a2 <> a1) mempty (y : map toIProperty cs1)
|
||||||
|
|
||||||
-- | Results of actions, with color.
|
instance Combines (Property NoInfo) (Property NoInfo) where
|
||||||
class ActionResult a where
|
requires (SProperty d1 a1 cs1) y@(SProperty _d2 a2 _cs2) =
|
||||||
getActionResult :: a -> (String, ColorIntensity, Color)
|
SProperty d1 (a2 <> a1) (y : cs1)
|
||||||
|
|
||||||
instance ActionResult Bool where
|
instance Combines RevertableProperty (Property HasInfo) where
|
||||||
getActionResult False = ("failed", Vivid, Red)
|
requires (RevertableProperty p1 p2) y =
|
||||||
getActionResult True = ("done", Dull, Green)
|
RevertableProperty (p1 `requires` y) p2
|
||||||
|
|
||||||
instance ActionResult Result where
|
instance Combines RevertableProperty (Property NoInfo) where
|
||||||
getActionResult NoChange = ("ok", Dull, Green)
|
requires (RevertableProperty p1 p2) y =
|
||||||
getActionResult MadeChange = ("done", Vivid, Green)
|
RevertableProperty (p1 `requires` toIProperty y) p2
|
||||||
getActionResult FailedChange = ("failed", Vivid, Red)
|
|
||||||
|
|
||||||
data CmdLine
|
instance Combines RevertableProperty RevertableProperty where
|
||||||
= Run HostName
|
requires (RevertableProperty x1 x2) (RevertableProperty y1 y2) =
|
||||||
| Spin [HostName] (Maybe HostName)
|
RevertableProperty
|
||||||
| SimpleRun HostName
|
(x1 `requires` y1)
|
||||||
| Set PrivDataField Context
|
-- when reverting, run actions in reverse order
|
||||||
| Dump PrivDataField Context
|
(y2 `requires` x2)
|
||||||
| Edit PrivDataField Context
|
|
||||||
| ListFields
|
|
||||||
| AddKey String
|
|
||||||
| Merge
|
|
||||||
| Serialized CmdLine
|
|
||||||
| Continue CmdLine
|
|
||||||
| Update (Maybe HostName)
|
|
||||||
| Relay HostName
|
|
||||||
| DockerInit HostName
|
|
||||||
| DockerChain HostName String
|
|
||||||
| ChrootChain HostName FilePath Bool Bool
|
|
||||||
| GitPush Fd Fd
|
|
||||||
deriving (Read, Show, Eq)
|
|
||||||
|
|
||||||
-- | Information about a host.
|
-- | Information about a host.
|
||||||
data Info = Info
|
data Info = Info
|
||||||
{ _os :: Val System
|
{ _os :: Val System
|
||||||
, _privDataFields :: S.Set (PrivDataField, HostContext)
|
, _privData :: S.Set (PrivDataField, Maybe PrivDataSourceDesc, HostContext)
|
||||||
, _sshPubKey :: M.Map SshKeyType String
|
, _sshPubKey :: M.Map SshKeyType String
|
||||||
, _aliases :: S.Set HostName
|
, _aliases :: S.Set HostName
|
||||||
, _dns :: S.Set Dns.Record
|
, _dns :: S.Set Dns.Record
|
||||||
|
@ -190,7 +285,7 @@ instance Monoid Info where
|
||||||
mempty = Info mempty mempty mempty mempty mempty mempty mempty mempty
|
mempty = Info mempty mempty mempty mempty mempty mempty mempty mempty
|
||||||
mappend old new = Info
|
mappend old new = Info
|
||||||
{ _os = _os old <> _os new
|
{ _os = _os old <> _os new
|
||||||
, _privDataFields = _privDataFields old <> _privDataFields new
|
, _privData = _privData old <> _privData new
|
||||||
, _sshPubKey = _sshPubKey new `M.union` _sshPubKey old
|
, _sshPubKey = _sshPubKey new `M.union` _sshPubKey old
|
||||||
, _aliases = _aliases old <> _aliases new
|
, _aliases = _aliases old <> _aliases new
|
||||||
, _dns = _dns old <> _dns new
|
, _dns = _dns old <> _dns new
|
||||||
|
@ -202,7 +297,7 @@ instance Monoid Info where
|
||||||
instance Empty Info where
|
instance Empty Info where
|
||||||
isEmpty i = and
|
isEmpty i = and
|
||||||
[ isEmpty (_os i)
|
[ isEmpty (_os i)
|
||||||
, isEmpty (_privDataFields i)
|
, isEmpty (_privData i)
|
||||||
, isEmpty (_sshPubKey i)
|
, isEmpty (_sshPubKey i)
|
||||||
, isEmpty (_aliases i)
|
, isEmpty (_aliases i)
|
||||||
, isEmpty (_dns i)
|
, isEmpty (_dns i)
|
||||||
|
@ -210,26 +305,3 @@ instance Empty Info where
|
||||||
, isEmpty (_dockerinfo i)
|
, isEmpty (_dockerinfo i)
|
||||||
, isEmpty (_chrootinfo i)
|
, isEmpty (_chrootinfo i)
|
||||||
]
|
]
|
||||||
|
|
||||||
data Val a = Val a | NoVal
|
|
||||||
deriving (Eq, Show)
|
|
||||||
|
|
||||||
instance Monoid (Val a) where
|
|
||||||
mempty = NoVal
|
|
||||||
mappend old new = case new of
|
|
||||||
NoVal -> old
|
|
||||||
_ -> new
|
|
||||||
|
|
||||||
instance Empty (Val a) where
|
|
||||||
isEmpty NoVal = True
|
|
||||||
isEmpty _ = False
|
|
||||||
|
|
||||||
fromVal :: Val a -> Maybe a
|
|
||||||
fromVal (Val a) = Just a
|
|
||||||
fromVal NoVal = Nothing
|
|
||||||
|
|
||||||
type RunLog = [EndAction]
|
|
||||||
|
|
||||||
-- | 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)
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
module Propellor.Types.CmdLine where
|
||||||
|
|
||||||
|
import Propellor.Types.OS
|
||||||
|
import Propellor.Types.PrivData
|
||||||
|
|
||||||
|
import System.Posix.Types
|
||||||
|
|
||||||
|
data CmdLine
|
||||||
|
= Run HostName
|
||||||
|
| Spin [HostName] (Maybe HostName)
|
||||||
|
| SimpleRun HostName
|
||||||
|
| Set PrivDataField Context
|
||||||
|
| Dump PrivDataField Context
|
||||||
|
| Edit PrivDataField Context
|
||||||
|
| ListFields
|
||||||
|
| AddKey String
|
||||||
|
| Merge
|
||||||
|
| Serialized CmdLine
|
||||||
|
| Continue CmdLine
|
||||||
|
| Update (Maybe HostName)
|
||||||
|
| Relay HostName
|
||||||
|
| DockerInit HostName
|
||||||
|
| DockerChain HostName String
|
||||||
|
| ChrootChain HostName FilePath Bool Bool
|
||||||
|
| GitPush Fd Fd
|
||||||
|
deriving (Read, Show, Eq)
|
||||||
|
|
|
@ -24,9 +24,11 @@ data PrivDataSource
|
||||||
| PrivDataSourceFileFromCommand PrivDataField FilePath String
|
| PrivDataSourceFileFromCommand PrivDataField FilePath String
|
||||||
| PrivDataSource PrivDataField String
|
| PrivDataSource PrivDataField String
|
||||||
|
|
||||||
|
type PrivDataSourceDesc = String
|
||||||
|
|
||||||
class IsPrivDataSource s where
|
class IsPrivDataSource s where
|
||||||
privDataField :: s -> PrivDataField
|
privDataField :: s -> PrivDataField
|
||||||
describePrivDataSource :: s -> Maybe String
|
describePrivDataSource :: s -> Maybe PrivDataSourceDesc
|
||||||
|
|
||||||
instance IsPrivDataSource PrivDataField where
|
instance IsPrivDataSource PrivDataField where
|
||||||
privDataField = id
|
privDataField = id
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
module Propellor.Types.Result where
|
||||||
|
|
||||||
|
import Data.Monoid
|
||||||
|
import System.Console.ANSI
|
||||||
|
|
||||||
|
-- | There can be three results of satisfying a Property.
|
||||||
|
data Result = NoChange | MadeChange | FailedChange
|
||||||
|
deriving (Read, Show, Eq)
|
||||||
|
|
||||||
|
instance Monoid Result where
|
||||||
|
mempty = NoChange
|
||||||
|
|
||||||
|
mappend FailedChange _ = FailedChange
|
||||||
|
mappend _ FailedChange = FailedChange
|
||||||
|
mappend MadeChange _ = MadeChange
|
||||||
|
mappend _ MadeChange = MadeChange
|
||||||
|
mappend NoChange NoChange = NoChange
|
||||||
|
|
||||||
|
class ToResult t where
|
||||||
|
toResult :: t -> Result
|
||||||
|
|
||||||
|
instance ToResult Bool where
|
||||||
|
toResult False = FailedChange
|
||||||
|
toResult True = MadeChange
|
||||||
|
|
||||||
|
-- | Results of actions, with color.
|
||||||
|
class ActionResult a where
|
||||||
|
getActionResult :: a -> (String, ColorIntensity, Color)
|
||||||
|
|
||||||
|
instance ActionResult Bool where
|
||||||
|
getActionResult False = ("failed", Vivid, Red)
|
||||||
|
getActionResult True = ("done", Dull, Green)
|
||||||
|
|
||||||
|
instance ActionResult Result where
|
||||||
|
getActionResult NoChange = ("ok", Dull, Green)
|
||||||
|
getActionResult MadeChange = ("done", Vivid, Green)
|
||||||
|
getActionResult FailedChange = ("failed", Vivid, Red)
|
|
@ -0,0 +1,22 @@
|
||||||
|
module Propellor.Types.Val where
|
||||||
|
|
||||||
|
import Data.Monoid
|
||||||
|
|
||||||
|
import Propellor.Types.Empty
|
||||||
|
|
||||||
|
data Val a = Val a | NoVal
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
instance Monoid (Val a) where
|
||||||
|
mempty = NoVal
|
||||||
|
mappend old new = case new of
|
||||||
|
NoVal -> old
|
||||||
|
_ -> new
|
||||||
|
|
||||||
|
instance Empty (Val a) where
|
||||||
|
isEmpty NoVal = True
|
||||||
|
isEmpty _ = False
|
||||||
|
|
||||||
|
fromVal :: Val a -> Maybe a
|
||||||
|
fromVal (Val a) = Just a
|
||||||
|
fromVal NoVal = Nothing
|
|
@ -0,0 +1,161 @@
|
||||||
|
{- data size display and parsing
|
||||||
|
-
|
||||||
|
- Copyright 2011 Joey Hess <id@joeyh.name>
|
||||||
|
-
|
||||||
|
- License: BSD-2-clause
|
||||||
|
-
|
||||||
|
-
|
||||||
|
- And now a rant:
|
||||||
|
-
|
||||||
|
- In the beginning, we had powers of two, and they were good.
|
||||||
|
-
|
||||||
|
- Disk drive manufacturers noticed that some powers of two were
|
||||||
|
- sorta close to some powers of ten, and that rounding down to the nearest
|
||||||
|
- power of ten allowed them to advertise their drives were bigger. This
|
||||||
|
- was sorta annoying.
|
||||||
|
-
|
||||||
|
- Then drives got big. Really, really big. This was good.
|
||||||
|
-
|
||||||
|
- Except that the small rounding error perpretrated by the drive
|
||||||
|
- manufacturers suffered the fate of a small error, and became a large
|
||||||
|
- error. This was bad.
|
||||||
|
-
|
||||||
|
- So, a committee was formed. And it arrived at a committee-like decision,
|
||||||
|
- which satisfied noone, confused everyone, and made the world an uglier
|
||||||
|
- place. As with all committees, this was meh.
|
||||||
|
-
|
||||||
|
- And the drive manufacturers happily continued selling drives that are
|
||||||
|
- increasingly smaller than you'd expect, if you don't count on your
|
||||||
|
- fingers. But that are increasingly too big for anyone to much notice.
|
||||||
|
- This caused me to need git-annex.
|
||||||
|
-
|
||||||
|
- Thus, I use units here that I loathe. Because if I didn't, people would
|
||||||
|
- be confused that their drives seem the wrong size, and other people would
|
||||||
|
- complain at me for not being standards compliant. And we call this
|
||||||
|
- progress?
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Utility.DataUnits (
|
||||||
|
dataUnits,
|
||||||
|
storageUnits,
|
||||||
|
memoryUnits,
|
||||||
|
bandwidthUnits,
|
||||||
|
oldSchoolUnits,
|
||||||
|
Unit(..),
|
||||||
|
|
||||||
|
roughSize,
|
||||||
|
compareSizes,
|
||||||
|
readSize
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Data.List
|
||||||
|
import Data.Char
|
||||||
|
|
||||||
|
import Utility.HumanNumber
|
||||||
|
|
||||||
|
type ByteSize = Integer
|
||||||
|
type Name = String
|
||||||
|
type Abbrev = String
|
||||||
|
data Unit = Unit ByteSize Abbrev Name
|
||||||
|
deriving (Ord, Show, Eq)
|
||||||
|
|
||||||
|
dataUnits :: [Unit]
|
||||||
|
dataUnits = storageUnits ++ memoryUnits
|
||||||
|
|
||||||
|
{- Storage units are (stupidly) powers of ten. -}
|
||||||
|
storageUnits :: [Unit]
|
||||||
|
storageUnits =
|
||||||
|
[ Unit (p 8) "YB" "yottabyte"
|
||||||
|
, Unit (p 7) "ZB" "zettabyte"
|
||||||
|
, Unit (p 6) "EB" "exabyte"
|
||||||
|
, Unit (p 5) "PB" "petabyte"
|
||||||
|
, Unit (p 4) "TB" "terabyte"
|
||||||
|
, Unit (p 3) "GB" "gigabyte"
|
||||||
|
, Unit (p 2) "MB" "megabyte"
|
||||||
|
, Unit (p 1) "kB" "kilobyte" -- weird capitalization thanks to committe
|
||||||
|
, Unit (p 0) "B" "byte"
|
||||||
|
]
|
||||||
|
where
|
||||||
|
p :: Integer -> Integer
|
||||||
|
p n = 1000^n
|
||||||
|
|
||||||
|
{- Memory units are (stupidly named) powers of 2. -}
|
||||||
|
memoryUnits :: [Unit]
|
||||||
|
memoryUnits =
|
||||||
|
[ Unit (p 8) "YiB" "yobibyte"
|
||||||
|
, Unit (p 7) "ZiB" "zebibyte"
|
||||||
|
, Unit (p 6) "EiB" "exbibyte"
|
||||||
|
, Unit (p 5) "PiB" "pebibyte"
|
||||||
|
, Unit (p 4) "TiB" "tebibyte"
|
||||||
|
, Unit (p 3) "GiB" "gibibyte"
|
||||||
|
, Unit (p 2) "MiB" "mebibyte"
|
||||||
|
, Unit (p 1) "KiB" "kibibyte"
|
||||||
|
, Unit (p 0) "B" "byte"
|
||||||
|
]
|
||||||
|
where
|
||||||
|
p :: Integer -> Integer
|
||||||
|
p n = 2^(n*10)
|
||||||
|
|
||||||
|
{- Bandwidth units are only measured in bits if you're some crazy telco. -}
|
||||||
|
bandwidthUnits :: [Unit]
|
||||||
|
bandwidthUnits = error "stop trying to rip people off"
|
||||||
|
|
||||||
|
{- Do you yearn for the days when men were men and megabytes were megabytes? -}
|
||||||
|
oldSchoolUnits :: [Unit]
|
||||||
|
oldSchoolUnits = zipWith (curry mingle) storageUnits memoryUnits
|
||||||
|
where
|
||||||
|
mingle (Unit _ a n, Unit s' _ _) = Unit s' a n
|
||||||
|
|
||||||
|
{- approximate display of a particular number of bytes -}
|
||||||
|
roughSize :: [Unit] -> Bool -> ByteSize -> String
|
||||||
|
roughSize units short i
|
||||||
|
| i < 0 = '-' : findUnit units' (negate i)
|
||||||
|
| otherwise = findUnit units' i
|
||||||
|
where
|
||||||
|
units' = sortBy (flip compare) units -- largest first
|
||||||
|
|
||||||
|
findUnit (u@(Unit s _ _):us) i'
|
||||||
|
| i' >= s = showUnit i' u
|
||||||
|
| otherwise = findUnit us i'
|
||||||
|
findUnit [] i' = showUnit i' (last units') -- bytes
|
||||||
|
|
||||||
|
showUnit x (Unit size abbrev name) = s ++ " " ++ unit
|
||||||
|
where
|
||||||
|
v = (fromInteger x :: Double) / fromInteger size
|
||||||
|
s = showImprecise 2 v
|
||||||
|
unit
|
||||||
|
| short = abbrev
|
||||||
|
| s == "1" = name
|
||||||
|
| otherwise = name ++ "s"
|
||||||
|
|
||||||
|
{- displays comparison of two sizes -}
|
||||||
|
compareSizes :: [Unit] -> Bool -> ByteSize -> ByteSize -> String
|
||||||
|
compareSizes units abbrev old new
|
||||||
|
| old > new = roughSize units abbrev (old - new) ++ " smaller"
|
||||||
|
| old < new = roughSize units abbrev (new - old) ++ " larger"
|
||||||
|
| otherwise = "same"
|
||||||
|
|
||||||
|
{- Parses strings like "10 kilobytes" or "0.5tb". -}
|
||||||
|
readSize :: [Unit] -> String -> Maybe ByteSize
|
||||||
|
readSize units input
|
||||||
|
| null parsednum || null parsedunit = Nothing
|
||||||
|
| otherwise = Just $ round $ number * fromIntegral multiplier
|
||||||
|
where
|
||||||
|
(number, rest) = head parsednum
|
||||||
|
multiplier = head parsedunit
|
||||||
|
unitname = takeWhile isAlpha $ dropWhile isSpace rest
|
||||||
|
|
||||||
|
parsednum = reads input :: [(Double, String)]
|
||||||
|
parsedunit = lookupUnit units unitname
|
||||||
|
|
||||||
|
lookupUnit _ [] = [1] -- no unit given, assume bytes
|
||||||
|
lookupUnit [] _ = []
|
||||||
|
lookupUnit (Unit s a n:us) v
|
||||||
|
| a ~~ v || n ~~ v = [s]
|
||||||
|
| plural n ~~ v || a ~~ byteabbrev v = [s]
|
||||||
|
| otherwise = lookupUnit us v
|
||||||
|
|
||||||
|
a ~~ b = map toLower a == map toLower b
|
||||||
|
|
||||||
|
plural n = n ++ "s"
|
||||||
|
byteabbrev a = a ++ "b"
|
|
@ -0,0 +1,21 @@
|
||||||
|
{- numbers for humans
|
||||||
|
-
|
||||||
|
- Copyright 2012-2013 Joey Hess <id@joeyh.name>
|
||||||
|
-
|
||||||
|
- License: BSD-2-clause
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Utility.HumanNumber where
|
||||||
|
|
||||||
|
{- Displays a fractional value as a string with a limited number
|
||||||
|
- of decimal digits. -}
|
||||||
|
showImprecise :: RealFrac a => Int -> a -> String
|
||||||
|
showImprecise precision n
|
||||||
|
| precision == 0 || remainder == 0 = show (round n :: Integer)
|
||||||
|
| otherwise = show int ++ "." ++ striptrailing0s (pad0s $ show remainder)
|
||||||
|
where
|
||||||
|
int :: Integer
|
||||||
|
(int, frac) = properFraction n
|
||||||
|
remainder = round (frac * 10 ^ precision) :: Integer
|
||||||
|
pad0s s = replicate (precision - length s) '0' ++ s
|
||||||
|
striptrailing0s = reverse . dropWhile (== '0') . reverse
|
|
@ -16,13 +16,14 @@ tableWithHeader header rows = header : map linesep header : rows
|
||||||
where
|
where
|
||||||
linesep = map (const '-')
|
linesep = map (const '-')
|
||||||
|
|
||||||
-- | Formats a table to lines, automatically padding rows to the same size.
|
-- | Formats a table to lines, automatically padding columns to the same size.
|
||||||
formatTable :: Table -> [String]
|
formatTable :: Table -> [String]
|
||||||
formatTable table = map (\r -> unwords (map pad (zip r rowsizes))) table
|
formatTable table = map (\r -> unwords (map pad (zip r colsizes))) table
|
||||||
where
|
where
|
||||||
pad (cell, size) = cell ++ take (size - length cell) padding
|
pad (cell, size) = cell ++ take (size - length cell) padding
|
||||||
padding = repeat ' '
|
padding = repeat ' '
|
||||||
rowsizes = sumrows (map (map length) table)
|
colsizes = reverse $ (0:) $ drop 1 $ reverse $
|
||||||
sumrows [] = repeat 0
|
sumcols (map (map length) table)
|
||||||
sumrows [r] = r
|
sumcols [] = repeat 0
|
||||||
sumrows (r1:r2:rs) = sumrows $ map (uncurry max) (zip r1 r2) : rs
|
sumcols [r] = r
|
||||||
|
sumcols (r1:r2:rs) = sumcols $ map (uncurry max) (zip r1 r2) : rs
|
||||||
|
|
Loading…
Reference in New Issue