propellor 2.4.0 → 2.5.0
raw patch · 54 files changed
+886/−373 lines, 54 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Propellor.Property.Firewall: Port :: Port -> Rules
- Propellor.Property.Firewall: PortRange :: (Port, Port) -> Rules
- Propellor.Property.Firewall: instance Read Chain
- Propellor.Property.Firewall: instance Read ConnectionState
- Propellor.Property.Firewall: instance Read Proto
- Propellor.Property.Firewall: instance Read Rule
- Propellor.Property.Firewall: instance Read Rules
- Propellor.Property.Firewall: instance Read Target
- Propellor.Property.Firewall: type Port = Int
- Propellor.Property.SiteSpecific.GitAnnexBuilder: armelAutoBuilderContainer :: (System -> Image) -> Times -> TimeOut -> Container
- Propellor.Property.SiteSpecific.GitAnnexBuilder: armelCompanionContainer :: (System -> Image) -> Container
- Propellor.Property.SiteSpecific.GitAnnexBuilder: standardAutoBuilderContainer :: (System -> Image) -> Architecture -> Int -> TimeOut -> Container
+ Propellor.Property.Cmd: File :: FilePath -> CommandParam
+ Propellor.Property.Cmd: Param :: String -> CommandParam
+ Propellor.Property.Cmd: boolSystem :: FilePath -> [CommandParam] -> IO Bool
+ Propellor.Property.Cmd: boolSystemEnv :: FilePath -> [CommandParam] -> Maybe [(String, String)] -> IO Bool
+ Propellor.Property.Cmd: cmdPropertyEnv :: String -> [String] -> [(String, String)] -> Property NoInfo
+ Propellor.Property.Cmd: createProcess :: CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
+ Propellor.Property.Cmd: data CommandParam
+ Propellor.Property.Cmd: safeSystem :: FilePath -> [CommandParam] -> IO ExitCode
+ Propellor.Property.Cmd: safeSystemEnv :: FilePath -> [CommandParam] -> Maybe [(String, String)] -> IO ExitCode
+ Propellor.Property.Cmd: shellEscape :: String -> String
+ Propellor.Property.Cmd: type Script = [String]
+ Propellor.Property.Docker: class HasImage a
+ Propellor.Property.Docker: class Mountable p
+ Propellor.Property.Docker: class Publishable p
+ Propellor.Property.Docker: environment :: (String, String) -> Property HasInfo
+ Propellor.Property.Docker: getImageName :: HasImage a => a -> Image
+ Propellor.Property.Docker: imageBuilt :: HasImage c => FilePath -> c -> Property NoInfo
+ Propellor.Property.Docker: imagePulled :: HasImage c => c -> Property NoInfo
+ Propellor.Property.Docker: instance HasImage Container
+ Propellor.Property.Docker: instance HasImage Image
+ Propellor.Property.Docker: instance Mountable (Bound FilePath)
+ Propellor.Property.Docker: instance Mountable String
+ Propellor.Property.Docker: instance Publishable (Bound Port)
+ Propellor.Property.Docker: instance Publishable String
+ Propellor.Property.Firewall: DPort :: Port -> Rules
+ Propellor.Property.Firewall: DPortRange :: (Port, Port) -> Rules
+ Propellor.Property.Mount: mountPointsBelow :: FilePath -> IO [FilePath]
+ Propellor.Property.SiteSpecific.Branchable: server :: [Host] -> Property HasInfo
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: armAutoBuilder :: System -> Times -> TimeOut -> Property HasInfo
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: autoBuilderContainer :: (System -> Property HasInfo) -> System -> Times -> TimeOut -> Container
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: haskellPkgsInstalled :: String -> Property NoInfo
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: standardAutoBuilder :: System -> Property HasInfo
+ Propellor.Property.SiteSpecific.IABak: gitServer :: [Host] -> Property HasInfo
+ Propellor.Property.SiteSpecific.IABak: graphiteServer :: Property HasInfo
+ Propellor.Property.SiteSpecific.IABak: publicFace :: Property HasInfo
+ Propellor.Property.SiteSpecific.IABak: registrationServer :: [Host] -> Property HasInfo
+ Propellor.Property.SiteSpecific.IABak: repo :: String
+ Propellor.Property.SiteSpecific.IABak: userrepo :: String
+ Propellor.Property.Systemd: TCP :: Proto
+ Propellor.Property.Systemd: UDP :: Proto
+ Propellor.Property.Systemd: bind :: Bindable p => p -> RevertableProperty
+ Propellor.Property.Systemd: bindRo :: Bindable p => p -> RevertableProperty
+ Propellor.Property.Systemd: class Bindable a
+ Propellor.Property.Systemd: class Publishable a
+ Propellor.Property.Systemd: data Proto
+ Propellor.Property.Systemd: installed :: Property NoInfo
+ Propellor.Property.Systemd: instance Bindable (Bound FilePath)
+ Propellor.Property.Systemd: instance Bindable FilePath
+ Propellor.Property.Systemd: instance Publishable (Bound Port)
+ Propellor.Property.Systemd: instance Publishable (Proto, Bound Port)
+ Propellor.Property.Systemd: instance Publishable Port
+ Propellor.Property.Systemd: journald :: ServiceName
+ Propellor.Property.Systemd: linkJournal :: RevertableProperty
+ Propellor.Property.Systemd: networkd :: ServiceName
+ Propellor.Property.Systemd: privateNetwork :: RevertableProperty
+ Propellor.Property.Systemd: publish :: Publishable p => p -> RevertableProperty
+ Propellor.Property.Systemd: running :: ServiceName -> Property NoInfo
+ Propellor.Types.Container: (-<-) :: (hostv ~ v, containerv ~ v) => hostv -> containerv -> Bound v
+ Propellor.Types.Container: (->-) :: (containerv ~ v, hostv ~ v) => hostv -> containerv -> Bound v
+ Propellor.Types.Container: Bound :: v -> v -> Bound v
+ Propellor.Types.Container: containerSide :: Bound v -> v
+ Propellor.Types.Container: data Bound v
+ Propellor.Types.Container: hostSide :: Bound v -> v
+ Propellor.Types.Container: same :: v -> Bound v
+ Propellor.Types.OS: Port :: Int -> Port
+ Propellor.Types.OS: instance Eq Port
+ Propellor.Types.OS: instance Show Port
+ Propellor.Types.OS: newtype Port
- Propellor.Property.Chroot: propellChroot :: Chroot -> ([String] -> CreateProcess) -> Bool -> Property NoInfo
+ Propellor.Property.Chroot: propellChroot :: Chroot -> ([String] -> IO (CreateProcess, IO ())) -> Bool -> Property NoInfo
- Propellor.Property.Cmd: cmdProperty' :: String -> [String] -> [(String, String)] -> Property NoInfo
+ Propellor.Property.Cmd: cmdProperty' :: String -> [String] -> (CreateProcess -> CreateProcess) -> Property NoInfo
- Propellor.Property.Cmd: scriptProperty :: [String] -> Property NoInfo
+ Propellor.Property.Cmd: scriptProperty :: Script -> Property NoInfo
- Propellor.Property.Cmd: userScriptProperty :: User -> [String] -> Property NoInfo
+ Propellor.Property.Cmd: userScriptProperty :: User -> Script -> Property NoInfo
- Propellor.Property.Docker: publish :: String -> Property HasInfo
+ Propellor.Property.Docker: publish :: Publishable p => p -> Property HasInfo
- Propellor.Property.Docker: volume :: String -> Property HasInfo
+ Propellor.Property.Docker: volume :: Mountable v => v -> Property HasInfo
- Propellor.Property.SiteSpecific.GitAnnexBuilder: androidAutoBuilderContainer :: (System -> Image) -> Times -> TimeOut -> Container
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: androidAutoBuilderContainer :: Times -> TimeOut -> Container
- Propellor.Property.SiteSpecific.GitAnnexBuilder: androidContainer :: (IsProp (Property (CInfo NoInfo i)), Combines (Property NoInfo) (Property i)) => (System -> Image) -> ContainerName -> Property i -> FilePath -> Container
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: androidContainer :: (IsProp (Property (CInfo NoInfo i)), Combines (Property NoInfo) (Property i)) => MachineName -> Property i -> FilePath -> Container
Files
- CHANGELOG +31/−0
- config-joey.hs +106/−60
- debian/changelog +31/−0
- propellor.cabal +7/−4
- src/Propellor/Bootstrap.hs +0/−1
- src/Propellor/CmdLine.hs +12/−7
- src/Propellor/Git.hs +0/−1
- src/Propellor/Property/Apache.hs +0/−1
- src/Propellor/Property/Apt.hs +3/−3
- src/Propellor/Property/Chroot.hs +27/−10
- src/Propellor/Property/Cmd.hs +30/−8
- src/Propellor/Property/Cron.hs +0/−1
- src/Propellor/Property/Debootstrap.hs +1/−4
- src/Propellor/Property/Docker.hs +74/−19
- src/Propellor/Property/Firewall.hs +10/−14
- src/Propellor/Property/Git.hs +0/−1
- src/Propellor/Property/HostingProvider/DigitalOcean.hs +2/−2
- src/Propellor/Property/HostingProvider/Linode.hs +1/−1
- src/Propellor/Property/Mount.hs +11/−1
- src/Propellor/Property/OS.hs +0/−1
- src/Propellor/Property/Obnam.hs +0/−1
- src/Propellor/Property/Postfix.hs +4/−3
- src/Propellor/Property/Reboot.hs +0/−1
- src/Propellor/Property/Service.hs +0/−1
- src/Propellor/Property/SiteSpecific/Branchable.hs +66/−0
- src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs +49/−78
- src/Propellor/Property/SiteSpecific/GitHome.hs +0/−1
- src/Propellor/Property/SiteSpecific/IABak.hs +104/−0
- src/Propellor/Property/SiteSpecific/JoeySites.hs +1/−3
- src/Propellor/Property/Ssh.hs +0/−1
- src/Propellor/Property/Systemd.hs +136/−24
- src/Propellor/Shim.hs +0/−1
- src/Propellor/Spin.hs +13/−10
- src/Propellor/Ssh.hs +3/−3
- src/Propellor/Types/Container.hs +30/−0
- src/Propellor/Types/OS.hs +4/−0
- src/Utility/Data.hs +2/−0
- src/Utility/Directory.hs +2/−0
- src/Utility/Env.hs +2/−0
- src/Utility/Exception.hs +1/−0
- src/Utility/FileMode.hs +1/−12
- src/Utility/FileSystemEncoding.hs +1/−0
- src/Utility/LinuxMkLibs.hs +8/−7
- src/Utility/Misc.hs +6/−4
- src/Utility/Monad.hs +2/−0
- src/Utility/PartialPrelude.hs +2/−0
- src/Utility/Path.hs +2/−0
- src/Utility/PosixFiles.hs +1/−0
- src/Utility/Process.hs +42/−40
- src/Utility/QuickCheck.hs +1/−0
- src/Utility/SafeCommand.hs +50/−41
- src/Utility/Scheduled.hs +2/−1
- src/Utility/Tmp.hs +1/−0
- src/Utility/UserInfo.hs +4/−2
CHANGELOG view
@@ -1,3 +1,34 @@+propellor (2.5.0) unstable; urgency=medium++ * cmdProperty' renamed to cmdPropertyEnv to make way for a new,+ more generic cmdProperty' (API change)+ * Add docker image related properties.+ Thanks, Antoine Eiche.+ * Export CommandParam, boolSystem, safeSystem, shellEscape, and+ * createProcess from Propellor.Property.Cmd, so they are available+ for use in constricting your own Properties when using propellor+ as a library.+ * Improve enter-machine scripts for systemd-nspawn containers to unset most+ environment variables.+ * Fix Postfix.satellite bug; the default relayhost was set to the+ domain, not to smtp.domain as documented.+ * Mount /proc inside a chroot before provisioning it, to work around #787227+ * --spin now works when given a short hostname that only resolves to an+ ipv6 address.+ * Added publish property for systemd-spawn containers, for port publishing.+ (Needs systemd version 220.)+ * Added bind and bindRo properties for systemd-spawn containers.+ * Firewall: Port was changed to a newtype, and the Port and PortRange+ constructors of Rules were changed to DPort and DportRange, respectively.+ (API change)+ * Docker: volume and publish accept Bound FilePath and Bound Port,+ respectively. They also continue to accept Strings, for backwards+ compatibility.+ * Docker: Added environment property.+ Thanks Antoine Eiche.++ -- Joey Hess <id@joeyh.name> Tue, 09 Jun 2015 17:08:43 -0400+ propellor (2.4.0) unstable; urgency=medium * Propellor no longer supports Debian wheezy (oldstable).
config-joey.hs view
@@ -25,12 +25,14 @@ import qualified Propellor.Property.Gpg as Gpg import qualified Propellor.Property.Systemd as Systemd import qualified Propellor.Property.Journald as Journald+import qualified Propellor.Property.Chroot as Chroot import qualified Propellor.Property.OS as OS import qualified Propellor.Property.HostingProvider.CloudAtCost as CloudAtCost import qualified Propellor.Property.HostingProvider.Linode as Linode import qualified Propellor.Property.SiteSpecific.GitHome as GitHome import qualified Propellor.Property.SiteSpecific.GitAnnexBuilder as GitAnnexBuilder import qualified Propellor.Property.SiteSpecific.IABak as IABak+import qualified Propellor.Property.SiteSpecific.Branchable as Branchable import qualified Propellor.Property.SiteSpecific.JoeySites as JoeySites @@ -44,9 +46,11 @@ , gnu , clam , orca+ , honeybee , kite , elephant , beaver+ , pell , iabak ] ++ monsters @@ -72,8 +76,6 @@ & ipv6 "2001:4830:1600:187::2" -- sixxs tunnel & Apt.buildDep ["git-annex"] `period` Daily- & Docker.configured- ! Docker.docked gitAnnexAndroidDev & JoeySites.postfixClientRelay (Context "darkstar.kitenet.net") & JoeySites.dkimMilter@@ -81,7 +83,6 @@ gnu :: Host gnu = host "gnu.kitenet.net" & Apt.buildDep ["git-annex"] `period` Daily- & Docker.configured & JoeySites.postfixClientRelay (Context "gnu.kitenet.net") & JoeySites.dkimMilter@@ -95,18 +96,18 @@ & Ssh.randomHostKeys & Apt.unattendedUpgrades & Network.ipv6to4+ & Tor.isRelay & Tor.named "kite1" & Tor.bandwidthRate (Tor.PerMonth "400 GB") - & Docker.configured- & Docker.garbageCollected `period` Daily- & Docker.docked webserver+ & Systemd.nspawned webserver & File.dirExists "/var/www/html"- & File.notPresent "/var/www/html/index.html"- & "/var/www/index.html" `File.hasContent` ["hello, world"]+ & File.notPresent "/var/www/index.html"+ & "/var/www/html/index.html" `File.hasContent` ["hello, world"] & alias "helloworld.kitenet.net"- & Docker.docked oldusenetShellBox+ + & Systemd.nspawned oldusenetShellBox & JoeySites.scrollBox & alias "scroll.joeyh.name"@@ -127,16 +128,47 @@ & Apt.unattendedUpgrades & Postfix.satellite+ & Apt.serviceInstalledRunning "ntp" & Systemd.persistentJournal- & Docker.configured- & Docker.docked (GitAnnexBuilder.standardAutoBuilderContainer dockerImage "amd64" 15 "2h")- & Docker.docked (GitAnnexBuilder.standardAutoBuilderContainer dockerImage "i386" 45 "2h")- & Docker.docked (GitAnnexBuilder.armelCompanionContainer dockerImage)- & Docker.docked (GitAnnexBuilder.armelAutoBuilderContainer dockerImage (Cron.Times "1 3 * * *") "5h")- & Docker.docked (GitAnnexBuilder.androidAutoBuilderContainer dockerImage (Cron.Times "1 1 * * *") "3h")- & Docker.garbageCollected `period` Daily- & Apt.buildDep ["git-annex"] `period` Daily + & Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer+ GitAnnexBuilder.standardAutoBuilder+ (System (Debian Unstable) "amd64") fifteenpast "2h")+ & Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer+ GitAnnexBuilder.standardAutoBuilder+ (System (Debian Unstable) "i386") fifteenpast "2h")+ & Systemd.nspawned (GitAnnexBuilder.androidAutoBuilderContainer+ (Cron.Times "1 1 * * *") "3h")+ where+ fifteenpast = Cron.Times "15 * * * *"++honeybee :: Host+honeybee = standardSystem "honeybee.kitenet.net" Testing "armhf"+ [ "Arm git-annex build box." ]+ & ipv6 "2001:4830:1600:187::2"++ -- No unattended upgrades as there is currently no console access.+ -- (Also, system is not currently running a stock kernel,+ -- although it should be able to.)+ & Postfix.satellite+ & Apt.serviceInstalledRunning "aiccu"+ & Apt.serviceInstalledRunning "swapspace"+ & Apt.serviceInstalledRunning "ntp"++ -- Not using systemd-nspawn because it's broken (kernel issue?)+ -- & Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer+ -- GitAnnexBuilder.armAutoBuilder+ -- builderos Cron.Daily "22h")+ & Chroot.provisioned + (Chroot.debootstrapped builderos mempty "/var/lib/container/armel-git-annex-builder"+ & "/etc/timezone" `File.hasContent` ["America/New_York"]+ & GitAnnexBuilder.armAutoBuilder+ builderos (Cron.Times "1 1 * * *") "12h"+ )+ where+ -- Using unstable to get new enough ghc for TH on arm.+ builderos = System (Debian Unstable) "armel"+ -- This is not a complete description of kite, since it's a -- multiuser system with eg, user passwords that are not deployed -- with propellor.@@ -220,9 +252,6 @@ , "zsh" ] - & Docker.configured- & Docker.garbageCollected `period` Daily- & alias "nntp.olduse.net" & JoeySites.oldUseNetServer hosts @@ -279,13 +308,14 @@ & myDnsSecondary & Docker.configured- & Docker.docked oldusenetShellBox & Docker.docked openidProvider `requires` Apt.serviceInstalledRunning "ntp" & Docker.docked ancientKitenet & Docker.docked jerryPlay & Docker.garbageCollected `period` (Weekly (Just 1)) + & Systemd.nspawned oldusenetShellBox+ & JoeySites.scrollBox & alias "scroll.joeyh.name" & alias "eu.scroll.joeyh.name"@@ -293,7 +323,7 @@ -- For https port 443, shellinabox with ssh login to -- kitenet.net & alias "shell.kitenet.net"- & Docker.docked kiteShellBox+ & Systemd.nspawned kiteShellBox -- Nothing is using http port 80, so listen on -- that port for ssh, for traveling on bad networks that -- block 22.@@ -311,6 +341,31 @@ & Cron.niceJob "system disk backed up" Cron.Weekly (User "root") "/" "rsync -a -x / /home/joey/lib/backup/beaver.kitenet.net/" +-- Branchable is not completely deployed with propellor yet.+pell :: Host+pell = host "pell.branchable.com"+ & alias "branchable.com"+ & ipv4 "66.228.46.55"+ & ipv6 "2600:3c03::f03c:91ff:fedf:c0e5"+ + -- All the websites I host at branchable that don't use+ -- branchable.com dns.+ & alias "olduse.net"+ & alias "www.olduse.net"+ & alias "www.kitenet.net"+ & alias "joeyh.name"+ & alias "campaign.joeyh.name"+ & alias "ikiwiki.info"+ & alias "git.ikiwiki.info"+ & alias "l10n.ikiwiki.info"+ & alias "dist-bugs.kitenet.net"+ & alias "family.kitenet.net"++ & Apt.installed ["linux-image-amd64"]+ & Linode.chainPVGrub 5+ & Apt.unattendedUpgrades+ & Branchable.server hosts+ iabak :: Host iabak = host "iabak.archiveteam.org" & ipv4 "124.6.40.227"@@ -338,28 +393,28 @@ & IABak.gitServer monsters & IABak.registrationServer monsters & IABak.graphiteServer+ & IABak.publicFace where admins = map User ["joey", "db48x"] --' __|II| ,. ---- __|II|II|__ ( \_,/\ --'-------'\o/-'-.-'-.-'-.- __|II|II|II|II|___/ __/ -'-.-'-.-'-.-'-.-'-.-'---------------------------- | [Docker] / --------------------------+-------------------------- | [Containers] / -------------------------- -------------------------- : / --------------------------- --------------------------- \____, o ,' ---------------------------- ---------------------------- '--,___________,' ----------------------------- -- Simple web server, publishing the outside host's /var/www-webserver :: Docker.Container+webserver :: Systemd.Container webserver = standardStableContainer "webserver"- & Docker.publish "80:80"- & Docker.volume "/var/www:/var/www"+ & Systemd.bind "/var/www" & Apt.serviceInstalledRunning "apache2" -- My own openid provider. Uses php, so containerized for security -- and administrative sanity. openidProvider :: Docker.Container-openidProvider = standardStableContainer "openid-provider"+openidProvider = standardStableDockerContainer "openid-provider" & alias "openid.kitenet.net" & Docker.publish "8081:80" & OpenId.providerFor [User "joey", User "liw"]@@ -367,39 +422,30 @@ -- Exhibit: kite's 90's website. ancientKitenet :: Docker.Container-ancientKitenet = standardStableContainer "ancient-kitenet"+ancientKitenet = standardStableDockerContainer "ancient-kitenet" & alias "ancient.kitenet.net" & Docker.publish "1994:80" & Apt.serviceInstalledRunning "apache2"- & Git.cloned (User "root") "git://kitenet-net.branchable.com/" "/var/www"+ & Git.cloned (User "root") "git://kitenet-net.branchable.com/" "/var/www/html" (Just "remotes/origin/old-kitenet.net") -oldusenetShellBox :: Docker.Container+oldusenetShellBox :: Systemd.Container oldusenetShellBox = standardStableContainer "oldusenet-shellbox" & alias "shell.olduse.net"- & Docker.publish "4200:4200" & JoeySites.oldUseNetShellBox --- for development of git-annex for android, using my git-annex work tree-gitAnnexAndroidDev :: Docker.Container-gitAnnexAndroidDev = GitAnnexBuilder.androidContainer dockerImage "android-git-annex" doNothing gitannexdir- & Docker.volume ("/home/joey/src/git-annex:" ++ gitannexdir)- where- gitannexdir = GitAnnexBuilder.homedir </> "git-annex"- jerryPlay :: Docker.Container-jerryPlay = standardContainer "jerryplay" Unstable "amd64"+jerryPlay = standardDockerContainer "jerryplay" Unstable "amd64" & alias "jerryplay.kitenet.net" & Docker.publish "2202:22" & Docker.publish "8001:80" & Apt.installed ["ssh"] & User.hasSomePassword (User "root") & Ssh.permitRootLogin True- -kiteShellBox :: Docker.Container++kiteShellBox :: Systemd.Container kiteShellBox = standardStableContainer "kiteshellbox" & JoeySites.kiteShellBox- & Docker.publish "443:443" type Motd = [String] @@ -430,16 +476,29 @@ & Apt.removed ["exim4", "exim4-daemon-light", "exim4-config", "exim4-base"] `onChange` Apt.autoRemove -standardStableContainer :: Docker.ContainerName -> Docker.Container-standardStableContainer name = standardContainer name (Stable "wheezy") "amd64"- -- This is my standard container setup, Featuring automatic upgrades.-standardContainer :: Docker.ContainerName -> DebianSuite -> Architecture -> Docker.Container-standardContainer name suite arch = Docker.container name (dockerImage system)+standardContainer :: Systemd.MachineName -> DebianSuite -> Architecture -> Systemd.Container+standardContainer name suite arch = Systemd.container name chroot & os system & Apt.stdSourcesList `onChange` Apt.upgrade & Apt.unattendedUpgrades & Apt.cacheCleaned+ where+ system = System (Debian suite) arch+ chroot = Chroot.debootstrapped system mempty++standardStableContainer :: Systemd.MachineName -> Systemd.Container+standardStableContainer name = standardContainer name (Stable "jessie") "amd64"++standardStableDockerContainer :: Docker.ContainerName -> Docker.Container+standardStableDockerContainer name = standardDockerContainer name (Stable "jessie") "amd64"++standardDockerContainer :: Docker.ContainerName -> DebianSuite -> Architecture -> Docker.Container+standardDockerContainer name suite arch = Docker.container name (dockerImage system)+ & os system+ & Apt.stdSourcesList `onChange` Apt.upgrade+ & Apt.unattendedUpgrades+ & Apt.cacheCleaned & Docker.tweaked where system = System (Debian suite) arch@@ -491,19 +550,6 @@ & ipv6 "2001:4978:f:2d9::2" , host "mouse.kitenet.net" & ipv6 "2001:4830:1600:492::2"- , host "branchable.com"- & ipv4 "66.228.46.55"- & ipv6 "2600:3c03::f03c:91ff:fedf:c0e5"- & alias "olduse.net"- & alias "www.olduse.net"- & alias "www.kitenet.net"- & alias "joeyh.name"- & alias "campaign.joeyh.name"- & alias "ikiwiki.info"- & alias "git.ikiwiki.info"- & alias "l10n.ikiwiki.info"- & alias "dist-bugs.kitenet.net"- & alias "family.kitenet.net" , host "animx" & ipv4 "76.7.162.101" & ipv4 "76.7.162.186"
debian/changelog view
@@ -1,3 +1,34 @@+propellor (2.5.0) unstable; urgency=medium++ * cmdProperty' renamed to cmdPropertyEnv to make way for a new,+ more generic cmdProperty' (API change)+ * Add docker image related properties.+ Thanks, Antoine Eiche.+ * Export CommandParam, boolSystem, safeSystem, shellEscape, and+ * createProcess from Propellor.Property.Cmd, so they are available+ for use in constricting your own Properties when using propellor+ as a library.+ * Improve enter-machine scripts for systemd-nspawn containers to unset most+ environment variables.+ * Fix Postfix.satellite bug; the default relayhost was set to the+ domain, not to smtp.domain as documented.+ * Mount /proc inside a chroot before provisioning it, to work around #787227+ * --spin now works when given a short hostname that only resolves to an+ ipv6 address.+ * Added publish property for systemd-spawn containers, for port publishing.+ (Needs systemd version 220.)+ * Added bind and bindRo properties for systemd-spawn containers.+ * Firewall: Port was changed to a newtype, and the Port and PortRange+ constructors of Rules were changed to DPort and DportRange, respectively.+ (API change)+ * Docker: volume and publish accept Bound FilePath and Bound Port,+ respectively. They also continue to accept Strings, for backwards+ compatibility.+ * Docker: Added environment property.+ Thanks Antoine Eiche.++ -- Joey Hess <id@joeyh.name> Tue, 09 Jun 2015 17:08:43 -0400+ propellor (2.4.0) unstable; urgency=medium * Propellor no longer supports Debian wheezy (oldstable).
propellor.cabal view
@@ -1,5 +1,5 @@ Name: propellor-Version: 2.4.0+Version: 2.5.0 Cabal-Version: >= 1.6 License: BSD3 Maintainer: Joey Hess <id@joeyh.name>@@ -33,7 +33,7 @@ Executable propellor Main-Is: wrapper.hs- GHC-Options: -Wall -threaded -O0+ GHC-Options: -threaded -O0 -Wall -fno-warn-tabs Hs-Source-Dirs: src Build-Depends: MissingH, directory, filepath, base >= 4.5, base < 5, IfElse, process, bytestring, hslogger, unix-compat, ansi-terminal,@@ -45,7 +45,7 @@ Executable propellor-config Main-Is: config.hs- GHC-Options: -Wall -threaded -O0+ GHC-Options: -threaded -O0 -Wall -fno-warn-tabs Hs-Source-Dirs: src Build-Depends: MissingH, directory, filepath, base >= 4.5, base < 5, IfElse, process, bytestring, hslogger, unix-compat, ansi-terminal,@@ -56,7 +56,7 @@ Build-Depends: unix Library- GHC-Options: -Wall -O0+ GHC-Options: -O0 -Wall -fno-warn-tabs Hs-Source-Dirs: src Build-Depends: MissingH, directory, filepath, base >= 4.5, base < 5, IfElse, process, bytestring, hslogger, unix-compat, ansi-terminal,@@ -110,6 +110,8 @@ Propellor.Property.SiteSpecific.GitHome Propellor.Property.SiteSpecific.JoeySites Propellor.Property.SiteSpecific.GitAnnexBuilder+ Propellor.Property.SiteSpecific.Branchable+ Propellor.Property.SiteSpecific.IABak Propellor.PropAccum Propellor.CmdLine Propellor.Info@@ -119,6 +121,7 @@ Propellor.Exception Propellor.Types Propellor.Types.Chroot+ Propellor.Types.Container Propellor.Types.Docker Propellor.Types.Dns Propellor.Types.Empty
src/Propellor/Bootstrap.hs view
@@ -6,7 +6,6 @@ ) where import Propellor-import Utility.SafeCommand import System.Posix.Files import Data.List
src/Propellor/CmdLine.hs view
@@ -7,7 +7,7 @@ import Data.List import System.Exit import System.PosixCompat-import qualified Network.BSD+import Network.Socket import Propellor import Propellor.Gpg@@ -18,7 +18,6 @@ import qualified Propellor.Property.Docker as Docker import qualified Propellor.Property.Chroot as Chroot import qualified Propellor.Shim as Shim-import Utility.SafeCommand usage :: Handle -> IO () usage h = hPutStrLn h $ unlines @@ -166,9 +165,15 @@ , next ) +-- Gets the fully qualified domain name, given a string that might be+-- a short name to look up in the DNS. hostname :: String -> IO HostName-hostname s- | "." `isInfixOf` s = pure s- | otherwise = do- h <- Network.BSD.getHostByName s- return (Network.BSD.hostName h)+hostname s = go =<< catchDefaultIO [] dnslookup+ where+ dnslookup = getAddrInfo (Just canonname) (Just s) Nothing+ canonname = defaultHints { addrFlags = [AI_CANONNAME] }+ go (AddrInfo { addrCanonName = Just v } : _) = pure v+ go _+ | "." `isInfixOf` s = pure s -- assume it's a fqdn+ | otherwise = + error $ "cannot find host " ++ s ++ " in the DNS"
src/Propellor/Git.hs view
@@ -3,7 +3,6 @@ import Propellor import Propellor.PrivData.Paths import Propellor.Gpg-import Utility.SafeCommand import Utility.FileMode getCurrentBranch :: IO String
src/Propellor/Property/Apache.hs view
@@ -4,7 +4,6 @@ import qualified Propellor.Property.File as File import qualified Propellor.Property.Apt as Apt import qualified Propellor.Property.Service as Service-import Utility.SafeCommand type ConfigFile = [String]
src/Propellor/Property/Apt.hs view
@@ -109,7 +109,7 @@ f = "/etc/apt/sources.list.d/" ++ basename ++ ".list" runApt :: [String] -> Property NoInfo-runApt ps = cmdProperty' "apt-get" ps noninteractiveEnv+runApt ps = cmdPropertyEnv "apt-get" ps noninteractiveEnv noninteractiveEnv :: [(String, String)] noninteractiveEnv =@@ -170,7 +170,7 @@ buildDepIn :: FilePath -> Property NoInfo buildDepIn dir = go `requires` installedMin ["devscripts", "equivs"] where- go = cmdProperty' "sh" ["-c", "cd '" ++ dir ++ "' && mk-build-deps debian/control --install --tool 'apt-get -y --no-install-recommends' --remove"]+ go = cmdPropertyEnv "sh" ["-c", "cd '" ++ dir ++ "' && mk-build-deps debian/control --install --tool 'apt-get -y --no-install-recommends' --remove"] noninteractiveEnv -- | Package installation may fail becuse the archive has changed.@@ -251,7 +251,7 @@ forM_ vals $ \(tmpl, tmpltype, value) -> hPutStrLn h $ unwords [package, tmpl, tmpltype, value] hClose h- reconfigure = cmdProperty' "dpkg-reconfigure" ["-fnone", package] noninteractiveEnv+ reconfigure = cmdPropertyEnv "dpkg-reconfigure" ["-fnone", package] noninteractiveEnv -- | Ensures that a service is installed and running. --
src/Propellor/Property/Chroot.hs view
@@ -19,7 +19,7 @@ import qualified Propellor.Property.Debootstrap as Debootstrap import qualified Propellor.Property.Systemd.Core as Systemd import qualified Propellor.Shim as Shim-import Utility.SafeCommand+import Propellor.Property.Mount import qualified Data.Map as M import Data.List.Utils@@ -56,8 +56,9 @@ -- | Ensures that the chroot exists and is provisioned according to its -- properties. ----- Reverting this property removes the chroot. Note that it does not ensure--- that any processes that might be running inside the chroot are stopped.+-- Reverting this property removes the chroot. Anything mounted inside it+-- is first unmounted. Note that it does not ensure that any processes+-- that might be running inside the chroot are stopped. provisioned :: Chroot -> RevertableProperty provisioned c = provisioned' (propigateChrootInfo c) c False @@ -69,7 +70,7 @@ where go desc a = propertyList (chrootDesc c desc) [a] - setup = propellChroot c (inChrootProcess c) systemdonly+ setup = propellChroot c (inChrootProcess (not systemdonly) c) systemdonly `requires` toProp built built = case (system, builderconf) of@@ -94,7 +95,7 @@ mempty { _chrootinfo = mempty { _chroots = M.singleton loc h } } -- | Propellor is run inside the chroot to provision it.-propellChroot :: Chroot -> ([String] -> CreateProcess) -> Bool -> Property NoInfo+propellChroot :: Chroot -> ([String] -> IO (CreateProcess, IO ())) -> Bool -> Property NoInfo propellChroot c@(Chroot loc _ _ _) mkproc systemdonly = property (chrootDesc c "provisioned") $ do let d = localdir </> shimdir c let me = localdir </> "propellor"@@ -117,19 +118,21 @@ , File localdir, File mntpnt ] )- + chainprovision shim = do parenthost <- asks hostName cmd <- liftIO $ toChain parenthost c systemdonly pe <- liftIO standardPathEnv- let p = mkproc+ (p, cleanup) <- liftIO $ mkproc [ shim , "--continue" , show cmd ] let p' = p { env = Just pe }- liftIO $ withHandle StdoutHandle createProcessSuccess p'+ r <- liftIO $ withHandle StdoutHandle createProcessSuccess p' processChainOutput+ liftIO cleanup+ return r toChain :: HostName -> Chroot -> Bool -> IO CmdLine toChain parenthost (Chroot loc _ _ _) systemdonly = do@@ -156,8 +159,22 @@ putStrLn $ "\n" ++ show r chain _ _ = errorMessage "bad chain command" -inChrootProcess :: Chroot -> [String] -> CreateProcess-inChrootProcess (Chroot loc _ _ _) cmd = proc "chroot" (loc:cmd)+inChrootProcess :: Bool -> Chroot -> [String] -> IO (CreateProcess, IO ())+inChrootProcess keepprocmounted (Chroot loc _ _ _) cmd = do+ mountproc+ return (proc "chroot" (loc:cmd), cleanup)+ where+ -- /proc needs to be mounted in the chroot for the linker to use+ -- /proc/self/exe which is necessary for some commands to work+ mountproc = unlessM (elem procloc <$> mountPointsBelow loc) $+ void $ mount "proc" "proc" procloc+ + procloc = loc </> "proc"++ cleanup+ | keepprocmounted = noop+ | otherwise = whenM (elem procloc <$> mountPointsBelow loc) $+ umountLazy procloc provisioningLock :: FilePath -> FilePath provisioningLock containerloc = "chroot" </> mungeloc containerloc ++ ".lock"
src/Propellor/Property/Cmd.hs view
@@ -1,10 +1,21 @@ {-# LANGUAGE PackageImports #-} module Propellor.Property.Cmd (+ -- * Properties for running commands and scripts cmdProperty, cmdProperty',+ cmdPropertyEnv,+ Script, scriptProperty, userScriptProperty,+ -- * Lower-level interface for running commands+ CommandParam(..),+ boolSystem,+ boolSystemEnv,+ safeSystem,+ safeSystemEnv,+ shellEscape,+ createProcess, ) where import Control.Applicative@@ -15,31 +26,42 @@ import Propellor.Property import Utility.SafeCommand import Utility.Env+import Utility.Process (createProcess, CreateProcess) -- | A property that can be satisfied by running a command. -- -- The command must exit 0 on success. cmdProperty :: String -> [String] -> Property NoInfo-cmdProperty cmd params = cmdProperty' cmd params []+cmdProperty cmd params = cmdProperty' cmd params id +cmdProperty' :: String -> [String] -> (CreateProcess -> CreateProcess) -> Property NoInfo+cmdProperty' cmd params mkprocess = property desc $ liftIO $ do+ toResult <$> boolSystem' cmd (map Param params) mkprocess+ where+ desc = unwords $ cmd : params+ -- | A property that can be satisfied by running a command,--- with added environment.-cmdProperty' :: String -> [String] -> [(String, String)] -> Property NoInfo-cmdProperty' cmd params env = property desc $ liftIO $ do+-- with added environment variables in addition to the standard+-- environment.+cmdPropertyEnv :: String -> [String] -> [(String, String)] -> Property NoInfo+cmdPropertyEnv cmd params env = property desc $ liftIO $ do env' <- addEntries env <$> getEnvironment toResult <$> boolSystemEnv cmd (map Param params) (Just env') where desc = unwords $ cmd : params --- | A property that can be satisfied by running a series of shell commands.-scriptProperty :: [String] -> Property NoInfo+-- | A series of shell commands. (Without a leading hashbang.)+type Script = [String]++-- | A property that can be satisfied by running a script.+scriptProperty :: Script -> Property NoInfo scriptProperty script = cmdProperty "sh" ["-c", shellcmd] where 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 script -- as user (cd'd to their home directory).-userScriptProperty :: User -> [String] -> Property NoInfo+userScriptProperty :: User -> Script -> Property NoInfo userScriptProperty (User user) script = cmdProperty "su" ["--shell", "/bin/sh", "-c", shellcmd, user] where shellcmd = intercalate " ; " ("set -e" : "cd" : script)
src/Propellor/Property/Cron.hs view
@@ -4,7 +4,6 @@ import qualified Propellor.Property.File as File import qualified Propellor.Property.Apt as Apt import Propellor.Bootstrap-import Utility.SafeCommand import Utility.FileMode import Data.Char
src/Propellor/Property/Debootstrap.hs view
@@ -15,7 +15,6 @@ import Propellor.Property.Chroot.Util import Propellor.Property.Mount import Utility.Path-import Utility.SafeCommand import Utility.FileMode import Data.List@@ -107,9 +106,7 @@ removetarget :: FilePath -> IO () removetarget target = do- submnts <- filter (\p -> simplifyPath p /= simplifyPath target)- . filter (dirContains target)- <$> mountPoints+ submnts <- mountPointsBelow target forM_ submnts umountLazy removeDirectoryRecursive target
src/Propellor/Property/Docker.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleContexts, TypeSynonymInstances, FlexibleInstances #-} -- | Docker support for propellor --@@ -11,24 +11,30 @@ configured, container, docked,+ imageBuilt,+ imagePulled, memoryLimited, garbageCollected, tweaked, Image, ContainerName, Container,+ HasImage(..), -- * Container configuration dns, hostname,+ Publishable, publish, expose, user,+ Mountable, volume, volumes_from, workdir, memory, cpuShares, link,+ environment, ContainerAlias, restartAlways, restartOnFailure,@@ -40,11 +46,12 @@ import Propellor hiding (init) import Propellor.Types.Docker+import Propellor.Types.Container import Propellor.Types.CmdLine import qualified Propellor.Property.File as File import qualified Propellor.Property.Apt as Apt+import qualified Propellor.Property.Cmd as Cmd import qualified Propellor.Shim as Shim-import Utility.SafeCommand import Utility.Path import Utility.ThreadScheduler @@ -78,6 +85,15 @@ -- | A docker container. data Container = Container Image Host +class HasImage a where+ getImageName :: a -> Image++instance HasImage Image where+ getImageName = id++instance HasImage Container where+ getImageName (Container i _) = i+ instance PropAccum Container where (Container i h) & p = Container i (h & p) (Container i h) &^ p = Container i (h &^ p)@@ -135,6 +151,23 @@ ] ] +-- | Build the image from a directory containing a Dockerfile.+imageBuilt :: HasImage c => FilePath -> c -> Property NoInfo+imageBuilt directory ctr = describe built msg+ where+ msg = "docker image " ++ image ++ " built from " ++ directory+ built = Cmd.cmdProperty' dockercmd ["build", "--tag", image, "./"] workDir+ workDir p = p { cwd = Just directory }+ image = getImageName ctr++-- | Pull the image from the standard Docker Hub registry.+imagePulled :: HasImage c => c -> Property NoInfo+imagePulled ctr = describe pulled msg+ where+ msg = "docker image " ++ image ++ " pulled"+ pulled = Cmd.cmdProperty dockercmd ["pull", image]+ image = getImageName ctr+ propigateContainerInfo :: (IsProp (Property i)) => Container -> Property i -> Property HasInfo propigateContainerInfo ctr@(Container _ h) p = propigateContainer ctr p' where@@ -225,10 +258,19 @@ name :: String -> Property HasInfo name = runProp "name" +class Publishable p where+ toPublish :: p -> String++instance Publishable (Bound Port) where+ toPublish p = show (hostSide p) ++ ":" ++ show (containerSide p)++-- | string format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort+instance Publishable String where+ toPublish = id+ -- | Publish a container's port to the host--- (format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort)-publish :: String -> Property HasInfo-publish = runProp "publish"+publish :: Publishable p => p -> Property HasInfo+publish = runProp "publish" . toPublish -- | Expose a container's port without publishing it. expose :: String -> Property HasInfo@@ -238,12 +280,22 @@ user :: String -> Property HasInfo user = runProp "user" --- | Mount a volume--- Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]+class Mountable p where+ toMount :: p -> String++instance Mountable (Bound FilePath) where+ toMount p = hostSide p ++ ":" ++ containerSide p++-- | string format: [host-dir]:[container-dir]:[rw|ro]+-- -- With just a directory, creates a volume in the container.-volume :: String -> Property HasInfo-volume = runProp "volume"+instance Mountable String where+ toMount = id +-- | Mount a volume+volume :: Mountable v => v -> Property HasInfo+volume = runProp "volume" . toMount+ -- | Mount a volume from the specified container into the current -- container. volumes_from :: ContainerName -> Property HasInfo@@ -297,6 +349,11 @@ restartNever :: Property HasInfo restartNever = runProp "restart" "no" +-- | Set environment variable with a tuple composed by the environment+-- variable name and its value.+environment :: (String, String) -> Property HasInfo+environment (k, v) = runProp "env" $ k ++ "=" ++ v+ -- | A container is identified by its name, and the host -- on which it's deployed. data ContainerId = ContainerId @@ -396,16 +453,14 @@ retry (n-1) a _ -> return v - go img = do- liftIO $ do- clearProvisionedFlag cid- createDirectoryIfMissing True (takeDirectory $ identFile cid)- shim <- liftIO $ Shim.setup (localdir </> "propellor") Nothing (localdir </> shimdir cid)- liftIO $ writeFile (identFile cid) (show ident)- ensureProperty $ property "run" $ liftIO $- toResult <$> runContainer img- (runps ++ ["-i", "-d", "-t"])- [shim, "--continue", show (DockerInit (fromContainerId cid))]+ go img = liftIO $ do+ clearProvisionedFlag cid+ createDirectoryIfMissing True (takeDirectory $ identFile cid)+ shim <- Shim.setup (localdir </> "propellor") Nothing (localdir </> shimdir cid)+ writeFile (identFile cid) (show ident)+ toResult <$> runContainer img+ (runps ++ ["-i", "-d", "-t"])+ [shim, "--continue", show (DockerInit (fromContainerId cid))] -- | Called when propellor is running inside a docker container. -- The string should be the container's ContainerId.
src/Propellor/Property/Firewall.hs view
@@ -9,7 +9,6 @@ Target(..), Proto(..), Rules(..),- Port, ConnectionState(..) ) where @@ -18,7 +17,6 @@ import Data.List import Propellor-import Utility.SafeCommand import qualified Propellor.Property.Apt as Apt import qualified Propellor.Property.Network as Network @@ -46,8 +44,8 @@ toIpTableArg :: Rules -> [String] toIpTableArg Everything = [] toIpTableArg (Proto proto) = ["-p", map toLower $ show proto]-toIpTableArg (Port port) = ["--dport", show port]-toIpTableArg (PortRange (f,t)) = ["--dport", show f ++ ":" ++ show t]+toIpTableArg (DPort port) = ["--dport", show port]+toIpTableArg (DPortRange (f,t)) = ["--dport", show f ++ ":" ++ show t] toIpTableArg (IFace iface) = ["-i", iface] toIpTableArg (Ctstate states) = ["-m", "conntrack","--ctstate", concat $ intersperse "," (map show states)] toIpTableArg (r :- r') = toIpTableArg r <> toIpTableArg r'@@ -56,33 +54,31 @@ { ruleChain :: Chain , ruleTarget :: Target , ruleRules :: Rules- } deriving (Eq, Show, Read)+ } deriving (Eq, Show) data Chain = INPUT | OUTPUT | FORWARD- deriving (Eq,Show,Read)+ deriving (Eq, Show) data Target = ACCEPT | REJECT | DROP | LOG- deriving (Eq,Show,Read)+ deriving (Eq, Show) data Proto = TCP | UDP | ICMP- deriving (Eq,Show,Read)--type Port = Int+ deriving (Eq, Show) data ConnectionState = ESTABLISHED | RELATED | NEW | INVALID- deriving (Eq,Show,Read)+ deriving (Eq, Show) data Rules = Everything | Proto Proto -- ^There is actually some order dependency between proto and port so this should be a specific -- data type with proto + ports- | Port Port- | PortRange (Port,Port)+ | DPort Port+ | DPortRange (Port,Port) | IFace Network.Interface | Ctstate [ ConnectionState ] | Rules :- Rules -- ^Combine two rules- deriving (Eq,Show,Read)+ deriving (Eq, Show) infixl 0 :-
src/Propellor/Property/Git.hs view
@@ -4,7 +4,6 @@ import Propellor.Property.File import qualified Propellor.Property.Apt as Apt import qualified Propellor.Property.Service as Service-import Utility.SafeCommand import Data.List
src/Propellor/Property/HostingProvider/DigitalOcean.hs view
@@ -9,7 +9,7 @@ import Data.List --- Digital Ocean does not provide any way to boot+-- | Digital Ocean does not provide any way to boot -- the kernel provided by the distribution, except using kexec. -- Without this, some old, and perhaps insecure kernel will be used. --@@ -40,7 +40,7 @@ findVersion kernelver <$> readProcess "file" ("-L" : kernelimages) --- File output looks something like this, we want to unambiguously+-- | File output looks something like this, we want to unambiguously -- match the running kernel version: -- Linux kernel x86 boot executable bzImage, version 3.16-3-amd64 (debian-kernel@lists.debian.org) #1 SMP Debian 3.1, RO-rootFS, swap_dev 0x2, Normal VGA findVersion :: String -> String -> Bool
src/Propellor/Property/HostingProvider/Linode.hs view
@@ -6,7 +6,7 @@ import Utility.FileMode -- | 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 chaining to enable -- it. chainPVGrub :: Grub.TimeoutSecs -> Property NoInfo chainPVGrub = Grub.chainPVGrub "hd0" "xen/xvda"
src/Propellor/Property/Mount.hs view
@@ -1,23 +1,33 @@ module Propellor.Property.Mount where import Propellor-import Utility.SafeCommand+import Utility.Path type FsType = String type Source = String +-- | Lists all mount points of the system. mountPoints :: IO [FilePath] mountPoints = lines <$> readProcess "findmnt" ["-rn", "--output", "target"] +-- | Finds all filesystems mounted inside the specified directory.+mountPointsBelow :: FilePath -> IO [FilePath]+mountPointsBelow target = filter (\p -> simplifyPath p /= simplifyPath target)+ . filter (dirContains target)+ <$> mountPoints++-- | Filesystem type mounted at a given location. getFsType :: FilePath -> IO (Maybe FsType) getFsType mnt = catchDefaultIO Nothing $ headMaybe . lines <$> readProcess "findmnt" ["-n", mnt, "--output", "fstype"] +-- | Unmounts a device, lazily so any running processes don't block it. umountLazy :: FilePath -> IO () umountLazy mnt = unlessM (boolSystem "umount" [ Param "-l", Param mnt ]) $ errorMessage $ "failed unmounting " ++ mnt +-- | Mounts a device. mount :: FsType -> Source -> FilePath -> IO Bool mount fs src mnt = boolSystem "mount" [Param "-t", Param fs, Param src, Param mnt]
src/Propellor/Property/OS.hs view
@@ -16,7 +16,6 @@ import qualified Propellor.Property.Reboot as Reboot import Propellor.Property.Mount import Propellor.Property.Chroot.Util (stdPATH)-import Utility.SafeCommand import System.Posix.Files (rename, fileExist) import Control.Exception (throw)
src/Propellor/Property/Obnam.hs view
@@ -4,7 +4,6 @@ import qualified Propellor.Property.Apt as Apt import qualified Propellor.Property.Cron as Cron import qualified Propellor.Property.Gpg as Gpg-import Utility.SafeCommand import Data.List
src/Propellor/Property/Postfix.hs view
@@ -22,7 +22,8 @@ reloaded = Service.reloaded "postfix" -- | Configures postfix as a satellite system, which --- relays all mail through a relay host, which defaults to smtp.domain. +-- relays all mail through a relay host, which defaults to smtp.domain,+-- but can be changed by mainCf "relayhost" -- -- The smarthost may refuse to relay mail on to other domains, without -- futher coniguration/keys. But this should be enough to get cron job@@ -34,14 +35,14 @@ setup = trivial $ property "postfix satellite system" $ do hn <- asks hostName let (_, domain) = separate (== '.') hn- ensureProperties + ensureProperties [ Apt.reConfigure "postfix" [ ("postfix/main_mailer_type", "select", "Satellite system") , ("postfix/root_address", "string", "root") , ("postfix/destinations", "string", "localhost") , ("postfix/mailname", "string", hn) ]- , mainCf ("relayhost", domain)+ , mainCf ("relayhost", "smtp." ++ domain) `onChange` reloaded ]
src/Propellor/Property/Reboot.hs view
@@ -1,7 +1,6 @@ module Propellor.Property.Reboot where import Propellor-import Utility.SafeCommand now :: Property NoInfo now = cmdProperty "reboot" []
src/Propellor/Property/Service.hs view
@@ -1,7 +1,6 @@ module Propellor.Property.Service where import Propellor-import Utility.SafeCommand type ServiceName = String
+ src/Propellor/Property/SiteSpecific/Branchable.hs view
@@ -0,0 +1,66 @@+module Propellor.Property.SiteSpecific.Branchable where++import Propellor+import qualified Propellor.Property.Apt as Apt+import qualified Propellor.Property.File as File+import qualified Propellor.Property.User as User+import qualified Propellor.Property.Ssh as Ssh+import qualified Propellor.Property.Postfix as Postfix+import qualified Propellor.Property.Gpg as Gpg+import qualified Propellor.Property.Sudo as Sudo++server :: [Host] -> Property HasInfo+server hosts = propertyList "branchable server" $ props+ & "/etc/timezone" `File.hasContent` ["Etc/UTC"]+ & "/etc/locale.gen" `File.containsLines`+ [ "en_GB.UTF-8 UTF-8"+ , "en_US.UTF-8 UTF-8"+ , "fi_FI.UTF-8 UTF-8"+ ]+ `onChange` cmdProperty "locale-gen" []++ & Apt.installed ["etckeeper", "ssh", "popularity-contest"]+ & Apt.serviceInstalledRunning "apache2"+ & Apt.serviceInstalledRunning "ntp"++ & Apt.serviceInstalledRunning "openssh-server"+ & Ssh.passwordAuthentication False+ & Ssh.hostKeys (Context "branchable.com")+ [ (SshDsa, "ssh-dss AAAAB3NzaC1kc3MAAACBAK9HnfpyIm8aEhKuF5oz6KyaLwFs2oWeToVkqVuykyy5Y8jWDZPtkpv+1TeOnjcOvJSZ1cCqB8iXlsP9Dr5z98w5MfzsRQM2wIw0n+wvmpPmUhjVdGh+wTpfP9bcyFHhj/f1Ymdq9hEWB26bnf4pbTbJW2ip8ULshMvn5CQ/ugV3AAAAFQCAjpRd1fquRiIuLJMwej0VcyoZKQAAAIBe91Grvz/icL3nlqXYrifXyr9dsw8bPN+BMu+hQtFsQXNJBylxwf8FtbRlmvZXmRjdVYqFVyxSsrL2pMsWlds51iXOr9pdsPG5a4OgJyRHsveBz3tz6HgYYPcr3Oxp7C6G6wrzwsaGK862SgRp/bbD226k9dODRBy3ogMhk/MvAgAAAIEApfknql3vZbDVa88ZnwbNKDOv8L1hb6blbKAMt2vJbqJMvu3EP9CsP9hGyEQh5YCAl2F9KEU3bJXN1BG76b7CiYtWK95lpL1XmCCWnJBCcdEhw998GfJS424frPw7qGmXLxJKYxEyioB90/IDp2dC+WaLcLOYHM9SroCQTIK5A1g= root@pell")+ , (SshRsa, "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA1M0aNLgcgcgf0tkmt/8vCDZLok8Xixz7Nun9wB6NqVXxfzAR4te+zyO7FucVwyTY5QHmiwwpmyNfaC21AAILhXGm12SUKSAirF9BkQk7bhQuz4T/dPlEt3d3SxQ3OZlXtPp4LzXWOyS0OXSzIb+HeaDA+hFXlQnp/gE7RyAzR1+xhWPO7Mz1q5O/+4dXANnW32t6P7Puob6NsglVDpLrMRYjkO+0RgCVbYMzB5+UnkthkZsIINaYwsNhW2GKMKbRZeyp5en5t1NJprGXdw0BqdBqd/rcBpOxmhHE1U7rw+GS1uZwCFWWv0aZbaXEJ6wY7mETFkqs0QXi5jtoKn95Gw== root@pell")+ ]++ & Apt.installed ["procmail", "bsd-mailx"]+ & "/etc/aliases" `File.hasPrivContentExposed` (Context "branchable.com")+ `onChange` Postfix.newaliases+ & "/etc/mailname" `File.hasContent` ["branchable.com"]+ & Postfix.installed+ & Postfix.mainCf ("mailbox_command", "procmail -a \"$EXTENSION\"")+ + -- Obnam is run by a cron job in ikiwiki-hosting.+ & "/etc/obnam.conf" `File.hasContent`+ [ "[config]"+ , "repository = sftp://joey@eubackup.kitenet.net/home/joey/lib/backup/pell.obnam"+ , "log = /var/log/obnam.log"+ , "encrypt-with = " ++ obnamkey+ , "log-level = info"+ , "log-max = 1048576"+ , "keep = 7d,5w,12m"+ , "upload-queue-size = 128"+ , "lru-size = 128"+ ]+ & Gpg.keyImported (Gpg.GpgKeyId obnamkey) (User "root")+ & Ssh.keyImported SshRsa (User "root") (Context "branchable.com")+ & Ssh.knownHost hosts "eubackup.kitenet.net" (User "root")+ & Ssh.knownHost hosts "usw-s002.rsync.net" (User "root")++ & adminuser "joey"+ & adminuser "liw"+ where+ obnamkey = "41E1A9B9"+ adminuser u = propertyList ("admin user " ++ u) $ props+ & User.accountFor (User u)+ & User.hasSomePassword (User u)+ & Sudo.enabledFor (User u)+ & User.hasGroup (User u) (Group "adm")+ & User.hasGroup (User u) (Group "systemd-journal")
src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs view
@@ -6,9 +6,9 @@ import qualified Propellor.Property.Apt as Apt import qualified Propellor.Property.User as User import qualified Propellor.Property.Cron as Cron-import qualified Propellor.Property.Ssh as Ssh import qualified Propellor.Property.File as File-import qualified Propellor.Property.Docker as Docker+import qualified Propellor.Property.Systemd as Systemd+import qualified Propellor.Property.Chroot as Chroot import Propellor.Property.Cron (Times) builduser :: UserName@@ -48,8 +48,6 @@ tree :: Architecture -> Property HasInfo tree buildarch = combineProperties "gitannexbuilder tree" $ props & Apt.installed ["git"]- -- gitbuilderdir directory already exists when docker volume is used,- -- but with wrong owner. & File.dirExists gitbuilderdir & File.ownerGroup gitbuilderdir (User builduser) (Group builduser) & gitannexbuildercloned@@ -84,6 +82,13 @@ "alex", "happy", "c2hs" ] +haskellPkgsInstalled :: String -> Property NoInfo+haskellPkgsInstalled dir = flagFile go ("/haskellpkgsinstalled")+ where+ go = userScriptProperty (User builduser)+ [ "cd " ++ builddir ++ " && ./standalone/ " ++ dir ++ "/install-haskell-packages"+ ]+ -- Installs current versions of git-annex's deps from cabal, but only -- does so once. cabalDeps :: Property NoInfo@@ -92,46 +97,61 @@ go = userScriptProperty (User builduser) ["cabal update && cabal install git-annex --only-dependencies || true"] cabalupdated = homedir </> ".cabal" </> "packages" </> "hackage.haskell.org" </> "00-index.cache" -standardAutoBuilderContainer :: (System -> Docker.Image) -> Architecture -> Int -> TimeOut -> Docker.Container-standardAutoBuilderContainer dockerImage arch buildminute timeout = Docker.container (arch ++ "-git-annex-builder")- (dockerImage $ System (Debian Testing) arch)- & os (System (Debian Testing) arch)- & Apt.stdSourcesList- & Apt.installed ["systemd"]- & Apt.unattendedUpgrades- & User.accountFor (User builduser)- & tree arch- & buildDepsApt- & autobuilder arch (Cron.Times $ show buildminute ++ " * * * *") timeout- & Docker.tweaked+autoBuilderContainer :: (System -> Property HasInfo) -> System -> Times -> TimeOut -> Systemd.Container+autoBuilderContainer mkprop osver@(System _ arch) crontime timeout =+ Systemd.container name bootstrap+ & mkprop osver+ & buildDepsApt+ & autobuilder arch crontime timeout+ where+ name = arch ++ "-git-annex-builder"+ bootstrap = Chroot.debootstrapped osver mempty -androidAutoBuilderContainer :: (System -> Docker.Image) -> Times -> TimeOut -> Docker.Container-androidAutoBuilderContainer dockerImage crontimes timeout =- androidContainer dockerImage "android-git-annex-builder" (tree "android") builddir+standardAutoBuilder :: System -> Property HasInfo+standardAutoBuilder osver@(System _ arch) =+ propertyList "standard git-annex autobuilder" $ props+ & os osver+ & Apt.stdSourcesList & Apt.unattendedUpgrades+ & User.accountFor (User builduser)+ & tree arch++armAutoBuilder :: System -> Times -> TimeOut -> Property HasInfo+armAutoBuilder osver@(System _ arch) crontime timeout = + propertyList "arm git-annex autobuilder" $ props+ & standardAutoBuilder osver+ & buildDepsNoHaskellLibs+ -- Works around ghc crash with parallel builds on arm.+ & (homedir </> ".cabal" </> "config")+ `File.lacksLine` "jobs: $ncpus"+ -- Install patched haskell packages for portability to+ -- arm NAS's using old kernel versions.+ & haskellPkgsInstalled "linux"+ & autobuilder arch crontime timeout++androidAutoBuilderContainer :: Times -> TimeOut -> Systemd.Container+androidAutoBuilderContainer crontimes timeout =+ androidContainer "android-git-annex-builder" (tree "android") builddir+ & Apt.unattendedUpgrades & autobuilder "android" crontimes timeout -- Android is cross-built in a Debian i386 container, using the Android NDK. androidContainer :: (IsProp (Property (CInfo NoInfo i)), (Combines (Property NoInfo) (Property i)))- => (System -> Docker.Image)- -> Docker.ContainerName+ => Systemd.MachineName -> Property i -> FilePath- -> Docker.Container-androidContainer dockerImage name setupgitannexdir gitannexdir = Docker.container name- (dockerImage osver)+ -> Systemd.Container+androidContainer name setupgitannexdir gitannexdir = Systemd.container name bootstrap & os osver & Apt.stdSourcesList- & Apt.installed ["systemd"]- & Docker.tweaked & User.accountFor (User builduser) & File.dirExists gitbuilderdir & File.ownerGroup homedir (User builduser) (Group builduser)- & buildDepsApt & flagFile chrootsetup ("/chrootsetup") `requires` setupgitannexdir- & flagFile haskellpkgsinstalled ("/haskellpkgsinstalled")+ & buildDepsApt+ & haskellPkgsInstalled "android" where -- Use git-annex's android chroot setup script, which will install -- ghc-android and the NDK, all build deps, etc, in the home@@ -139,54 +159,5 @@ chrootsetup = scriptProperty [ "cd " ++ gitannexdir ++ " && ./standalone/android/buildchroot-inchroot" ]- haskellpkgsinstalled = userScriptProperty (User builduser)- [ "cd " ++ gitannexdir ++ " && ./standalone/android/install-haskell-packages"- ] osver = System (Debian Testing) "i386"---- armel builder has a companion container using amd64 that--- runs the build first to get TH splices. They need--- to have the same versions of all haskell libraries installed.-armelCompanionContainer :: (System -> Docker.Image) -> Docker.Container-armelCompanionContainer dockerImage = Docker.container "armel-git-annex-builder-companion"- (dockerImage $ System (Debian Unstable) "amd64")- & os (System (Debian Testing) "amd64")- & Apt.stdSourcesList- & Apt.installed ["systemd"]- -- This volume is shared with the armel builder.- & Docker.volume gitbuilderdir- & User.accountFor (User builduser)- -- Install current versions of build deps from cabal.- & tree "armel"- & buildDepsNoHaskellLibs- & cabalDeps- -- The armel builder can ssh to this companion.- & Docker.expose "22"- & Apt.serviceInstalledRunning "ssh"- & Ssh.authorizedKeys (User builduser) (Context "armel-git-annex-builder")- & Docker.tweaked--armelAutoBuilderContainer :: (System -> Docker.Image) -> Times -> TimeOut -> Docker.Container-armelAutoBuilderContainer dockerImage crontimes timeout = Docker.container "armel-git-annex-builder"- (dockerImage $ System (Debian Unstable) "armel")- & os (System (Debian Testing) "armel")- & Apt.stdSourcesList- & Apt.installed ["systemd"]- & Apt.installed ["openssh-client"]- & Docker.link "armel-git-annex-builder-companion" "companion"- & Docker.volumes_from "armel-git-annex-builder-companion"- & User.accountFor (User builduser)- -- TODO: automate installing haskell libs- -- (Currently have to run- -- git-annex/standalone/linux/install-haskell-packages- -- which is not fully automated.)- & buildDepsNoHaskellLibs- & autobuilder "armel" crontimes timeout- `requires` tree "armel"- & Ssh.keyImported SshRsa (User builduser) (Context "armel-git-annex-builder")- & trivial writecompanionaddress- & Docker.tweaked- where- writecompanionaddress = scriptProperty- [ "echo \"$COMPANION_PORT_22_TCP_ADDR\" > " ++ homedir </> "companion_address"- ] `describe` "companion_address file"+ bootstrap = Chroot.debootstrapped osver mempty
src/Propellor/Property/SiteSpecific/GitHome.hs view
@@ -3,7 +3,6 @@ import Propellor import qualified Propellor.Property.Apt as Apt import Propellor.Property.User-import Utility.SafeCommand -- | Clones Joey Hess's git home directory, and runs its fixups script. installedFor :: User -> Property NoInfo
+ src/Propellor/Property/SiteSpecific/IABak.hs view
@@ -0,0 +1,104 @@+module Propellor.Property.SiteSpecific.IABak where++import Propellor+import qualified Propellor.Property.Apt as Apt+import qualified Propellor.Property.Git as Git+import qualified Propellor.Property.Cron as Cron+import qualified Propellor.Property.File as File+import qualified Propellor.Property.Apache as Apache+import qualified Propellor.Property.User as User+import qualified Propellor.Property.Ssh as Ssh++repo :: String+repo = "https://github.com/ArchiveTeam/IA.BAK/"++userrepo :: String+userrepo = "git@gitlab.com:archiveteam/IA.bak.users.git"++publicFace :: Property HasInfo+publicFace = propertyList "iabak public face" $ props+ & Git.cloned (User "root") repo "/usr/local/IA.BAK" (Just "server")+ & Apt.serviceInstalledRunning "apache2"+ & Cron.niceJob "graph-gen" (Cron.Times "*/10 * * * *") (User "root") "/"+ "/usr/local/IA.BAK/web/graph-gen.sh"++gitServer :: [Host] -> Property HasInfo+gitServer knownhosts = propertyList "iabak git server" $ props+ & Git.cloned (User "root") repo "/usr/local/IA.BAK" (Just "server")+ & Git.cloned (User "root") repo "/usr/local/IA.BAK/client" (Just "master")+ & Ssh.keyImported SshRsa (User "root") (Context "IA.bak.users.git")+ & Ssh.knownHost knownhosts "gitlab.com" (User "root")+ & Git.cloned (User "root") userrepo "/usr/local/IA.BAK/pubkeys" (Just "master")+ & Apt.serviceInstalledRunning "apache2"+ & cmdProperty "ln" ["-sf", "/usr/local/IA.BAK/pushme.cgi", "/usr/lib/cgi-bin/pushme.cgi"]+ & File.containsLine "/etc/sudoers" "www-data ALL=NOPASSWD:/usr/local/IA.BAK/pushed.sh"+ & Cron.niceJob "shardstats" (Cron.Times "*/30 * * * *") (User "root") "/"+ "/usr/local/IA.BAK/shardstats-all"+ & Cron.niceJob "shardmaint" Cron.Daily (User "root") "/"+ "/usr/local/IA.BAK/shardmaint-fast; /usr/local/IA.BAK/shardmaint"++registrationServer :: [Host] -> Property HasInfo+registrationServer knownhosts = propertyList "iabak registration server" $ props+ & User.accountFor (User "registrar")+ & Ssh.keyImported SshRsa (User "registrar") (Context "IA.bak.users.git")+ & Ssh.knownHost knownhosts "gitlab.com" (User "registrar")+ & Git.cloned (User "registrar") repo "/home/registrar/IA.BAK" (Just "server")+ & Git.cloned (User "registrar") userrepo "/home/registrar/users" (Just "master")+ & Apt.serviceInstalledRunning "apache2"+ & Apt.installed ["perl", "perl-modules"]+ & cmdProperty "ln" ["-sf", "/home/registrar/IA.BAK/registrar/register.cgi", link]+ & cmdProperty "chown" ["-h", "registrar:registrar", link]+ & File.containsLine "/etc/sudoers" "www-data ALL=(registrar) NOPASSWD:/home/registrar/IA.BAK/registrar/register.pl"+ & Apt.installed ["kgb-client"]+ & File.hasPrivContentExposed "/etc/kgb-bot/kgb-client.conf" anyContext+ `requires` File.dirExists "/etc/kgb-bot/"+ where+ link = "/usr/lib/cgi-bin/register.cgi"++graphiteServer :: Property HasInfo+graphiteServer = propertyList "iabak graphite server" $ props+ & Apt.serviceInstalledRunning "apache2"+ & Apt.installed ["libapache2-mod-wsgi", "graphite-carbon", "graphite-web"]+ & File.hasContent "/etc/carbon/storage-schemas.conf"+ [ "[carbon]"+ , "pattern = ^carbon\\."+ , "retentions = 60:90d"+ , "[iabak-connections]"+ , "pattern = ^iabak\\.shardstats\\.connections"+ , "retentions = 1h:1y,3h:10y"+ , "[iabak-default]"+ , "pattern = ^iabak\\."+ , "retentions = 10m:30d,1h:1y,3h:10y"+ , "[default_1min_for_1day]"+ , "pattern = .*"+ ]+ & graphiteCSRF+ & cmdProperty "graphite-manage" ["syncdb", "--noinput"] `flagFile` "/etc/flagFiles/graphite-syncdb"+ & cmdProperty "graphite-manage" ["createsuperuser", "--noinput", "--username=joey", "--email=joey@localhost"] `flagFile` "/etc/flagFiles/graphite-user-joey"+ `flagFile` "/etc/graphite-superuser-joey"+ & cmdProperty "graphite-manage" ["createsuperuser", "--noinput", "--username=db48x", "--email=db48x@localhost"] `flagFile` "/etc/flagFiles/graphite-user-db48x"+ `flagFile` "/etc/graphite-superuser-db48x"+ -- TODO: deal with passwords somehow+ & File.ownerGroup "/var/lib/graphite/graphite.db" (User "_graphite") (Group "_graphite")+ & "/etc/apache2/ports.conf" `File.containsLine` "Listen 8080"+ `onChange` Apache.restarted+ & Apache.siteEnabled "iabak-graphite-web"+ [ "<VirtualHost *:8080>"+ , " WSGIDaemonProcess _graphite processes=5 threads=5 display-name='%{GROUP}' inactivity-timeout=120 user=_graphite group=_graphite"+ , " WSGIProcessGroup _graphite"+ , " WSGIImportScript /usr/share/graphite-web/graphite.wsgi process-group=_graphite application-group=%{GLOBAL}"+ , " WSGIScriptAlias / /usr/share/graphite-web/graphite.wsgi"+ , " Alias /content/ /usr/share/graphite-web/static/"+ , " <Location \"/content/\">"+ , " SetHandler None"+ , " </Location>"+ , " ErrorLog ${APACHE_LOG_DIR}/graphite-web_error.log"+ , " LogLevel warn"+ , " CustomLog ${APACHE_LOG_DIR}/graphite-web_access.log combined"+ , "</VirtualHost>"+ ]+ where+ graphiteCSRF = withPrivData (Password "csrf-token") (Context "iabak.archiveteam.org") $+ \gettoken -> property "graphite-web CSRF token" $+ gettoken $ \token -> ensureProperty $ File.containsLine+ "/etc/graphite/local_settings.py" ("SECRET_KEY = '"++ token ++"'")
src/Propellor/Property/SiteSpecific/JoeySites.hs view
@@ -15,7 +15,6 @@ import qualified Propellor.Property.Obnam as Obnam import qualified Propellor.Property.Apache as Apache import qualified Propellor.Property.Postfix as Postfix-import Utility.SafeCommand import Utility.FileMode import Data.List@@ -30,7 +29,6 @@ "libghc-bytestring-dev", "libghc-mtl-dev", "libghc-ncurses-dev", "libghc-random-dev", "libghc-monad-loops-dev", "libghc-text-dev", "libghc-ifelse-dev", "libghc-case-insensitive-dev",- "libghc-transformers-dev", "libghc-data-default-dev", "libghc-optparse-applicative-dev"] & userScriptProperty (User "scroll") [ "cd " ++ d </> "scroll"@@ -470,7 +468,7 @@ `requires` Ssh.knownHost hosts srchost (User "joey") where desc = "backups copied from " ++ srchost ++ " on boot"- cmd = "rsync -az --bwlimit=300K --partial --delete " ++ srchost ++ ":lib/backup/ " ++ destdir </> srchost+ cmd = "sleep 30m && rsync -az --bwlimit=300K --partial --delete " ++ srchost ++ ":lib/backup/ " ++ destdir </> srchost obnamRepos :: [String] -> Property NoInfo obnamRepos rs = propertyList ("obnam repos for " ++ unwords rs)
src/Propellor/Property/Ssh.hs view
@@ -24,7 +24,6 @@ import qualified Propellor.Property.File as File import qualified Propellor.Property.Service as Service import Propellor.Property.User-import Utility.SafeCommand import Utility.FileMode import System.PosixCompat
src/Propellor/Property/Systemd.hs view
@@ -1,31 +1,50 @@+{-# LANGUAGE FlexibleInstances #-}+ module Propellor.Property.Systemd (- module Propellor.Property.Systemd.Core,+ -- * Services ServiceName,- MachineName, started, stopped, enabled, disabled,+ running, restarted,- persistentJournal,+ networkd,+ journald,+ -- * Configuration+ installed, Option, configured,- journaldConfigured, daemonReloaded,+ -- * Journal+ persistentJournal,+ journaldConfigured,+ -- * Containers+ MachineName, Container, container, nspawned,+ -- * Container configuration containerCfg, resolvConfed,+ linkJournal,+ privateNetwork,+ module Propellor.Types.Container,+ Proto(..),+ Publishable,+ publish,+ Bindable,+ bind,+ bindRo, ) where import Propellor import Propellor.Types.Chroot+import Propellor.Types.Container import qualified Propellor.Property.Chroot as Chroot import qualified Propellor.Property.Apt as Apt import qualified Propellor.Property.File as File import Propellor.Property.Systemd.Core-import Utility.SafeCommand import Utility.FileMode import Data.List@@ -45,6 +64,9 @@ getProperties (Container _ _ h) = hostProperties h -- | Starts a systemd service.+--+-- Note that this does not configure systemd to start the service on boot,+-- it only ensures that the service is currently running. started :: ServiceName -> Property NoInfo started n = trivial $ cmdProperty "systemctl" ["start", n] `describe` ("service " ++ n ++ " started")@@ -55,6 +77,9 @@ `describe` ("service " ++ n ++ " stopped") -- | Enables a systemd service.+--+-- This does not ensure the service is started, it only configures systemd+-- to start it on boot. enabled :: ServiceName -> Property NoInfo enabled n = trivial $ cmdProperty "systemctl" ["enable", n] `describe` ("service " ++ n ++ " enabled")@@ -64,11 +89,23 @@ disabled n = trivial $ cmdProperty "systemctl" ["disable", n] `describe` ("service " ++ n ++ " disabled") +-- | Ensures that a service is both enabled and started+running :: ServiceName -> Property NoInfo+running n = trivial $ started n `requires` enabled n+ -- | Restarts a systemd service. restarted :: ServiceName -> Property NoInfo restarted n = trivial $ cmdProperty "systemctl" ["restart", n] `describe` ("service " ++ n ++ " restarted") +-- | The systemd-networkd service.+networkd :: ServiceName+networkd = "systemd-networkd"++-- | The systemd-journald service.+journald :: ServiceName+journald = "systemd-journald"+ -- | Enables persistent storage of the journal. persistentJournal :: Property NoInfo persistentJournal = check (not <$> doesDirectoryExist dir) $ @@ -102,15 +139,15 @@ | setting `isPrefixOf` l = Nothing | otherwise = Just l +-- | Causes systemd to reload its configuration files.+daemonReloaded :: Property NoInfo+daemonReloaded = trivial $ cmdProperty "systemctl" ["daemon-reload"]+ -- | 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.-daemonReloaded :: Property NoInfo-daemonReloaded = trivial $ cmdProperty "systemctl" ["daemon-reload"]+ `onChange` restarted journald -- | Defines a container with a given machine name. --@@ -123,6 +160,7 @@ container name mkchroot = Container name c h & os system & resolvConfed+ & linkJournal where c@(Chroot.Chroot _ system _ _) = mkchroot (containerDir name) h = Host name [] mempty@@ -153,8 +191,7 @@ -- Chroot provisioning is run in systemd-only mode, -- which sets up the chroot and ensures systemd and dbus are -- installed, but does not handle the other provisions.- chrootprovisioned = Chroot.provisioned'- (Chroot.propigateChrootInfo chroot) chroot True+ chrootprovisioned = Chroot.provisioned' (Chroot.propigateChrootInfo chroot) chroot True -- Use nsenter to enter container and and run propellor to -- finish provisioning.@@ -178,8 +215,14 @@ return $ unlines $ "# deployed by propellor" : map addparams ls addparams l- | "ExecStart=" `isPrefixOf` l =- l ++ " " ++ unwords (nspawnServiceParams cfg)+ | "ExecStart=" `isPrefixOf` l = unwords $+ [ "ExecStart = /usr/bin/systemd-nspawn"+ , "--quiet"+ , "--keep-unit"+ , "--boot"+ , "--directory=" ++ containerDir name+ , "--machine=%i"+ ] ++ nspawnServiceParams cfg | otherwise = l goodservicefile = (==)@@ -216,15 +259,19 @@ where setup = combineProperties ("generated " ++ enterScriptFile c) [ scriptfile `File.hasContent`- [ "#!/bin/sh"+ [ "#!/usr/bin/perl" , "# Generated by propellor"- , "pid=\"$(machinectl show " ++ shellEscape name ++ " -p Leader | cut -d= -f2)\" || true"- , "if [ -n \"$pid\" ]; then"- , "\tnsenter -p -u -n -i -m -t \"$pid\" \"$@\""- , "else"- , "\techo container not running >&2"- , "\texit 1"- , "fi"+ , "my $pid=`machinectl show " ++ shellEscape name ++ " -p Leader | cut -d= -f2`;"+ , "chomp $pid;"+ , "if (length $pid) {"+ , "\tforeach my $var (keys %ENV) {"+ , "\t\tdelete $ENV{$var} unless $var eq 'PATH' || $var eq 'TERM';"+ , "\t}"+ , "\texec('nsenter', '-p', '-u', '-n', '-i', '-m', '-t', $pid, @ARGV);"+ , "} else {"+ , "\tdie 'container not running';"+ , "}"+ , "exit(1);" ] , scriptfile `File.mode` combineModes (readModes ++ executeModes) ]@@ -234,8 +281,8 @@ enterScriptFile :: Container -> FilePath enterScriptFile (Container name _ _ ) = "/usr/local/bin/enter-" ++ mungename name -enterContainerProcess :: Container -> [String] -> CreateProcess-enterContainerProcess = proc . enterScriptFile+enterContainerProcess :: Container -> [String] -> IO (CreateProcess, IO ())+enterContainerProcess c ps = pure (proc (enterScriptFile c) ps, noop) nspawnServiceName :: MachineName -> ServiceName nspawnServiceName name = "systemd-nspawn@" ++ name ++ ".service"@@ -267,3 +314,68 @@ -- This property is enabled by default. Revert it to disable it. resolvConfed :: RevertableProperty resolvConfed = containerCfg "bind=/etc/resolv.conf"++-- | Link the container's journal to the host's if possible.+-- (Only works if the host has persistent journal enabled.)+--+-- This property is enabled by default. Revert it to disable it.+linkJournal :: RevertableProperty+linkJournal = containerCfg "link-journal=try-guest"++-- | Disconnect networking of the container from the host.+privateNetwork :: RevertableProperty+privateNetwork = containerCfg "private-network"++class Publishable a where+ toPublish :: a -> String++instance Publishable Port where+ toPublish (Port n) = show n++instance Publishable (Bound Port) where+ toPublish v = toPublish (hostSide v) ++ ":" ++ toPublish (containerSide v)++data Proto = TCP | UDP++instance Publishable (Proto, Bound Port) where+ toPublish (TCP, fp) = "tcp:" ++ toPublish fp+ toPublish (UDP, fp) = "udp:" ++ toPublish fp++-- | Publish a port from the container to the host.+-- +-- This feature was first added in systemd version 220.+--+-- This property is only needed (and will only work) if the container+-- is configured to use private networking. Also, networkd should be enabled+-- both inside the container, and on the host. For example:+--+-- > foo :: Host+-- > foo = host "foo.example.com"+-- > & Systemd.running Systemd.networkd+-- > & Systemd.nspawned webserver+-- >+-- > webserver :: Systemd.container+-- > webserver = Systemd.container "webserver" (Chroot.debootstrapped (System (Debian Testing) "amd64") mempty)+-- > & Systemd.privateNetwork+-- > & Systemd.running Systemd.networkd+-- > & Systemd.publish (Port 80 ->- Port 8080)+-- > & Apt.installedRunning "apache2"+publish :: Publishable p => p -> RevertableProperty+publish p = containerCfg $ "--port=" ++ toPublish p++class Bindable a where+ toBind :: a -> String++instance Bindable FilePath where+ toBind f = f++instance Bindable (Bound FilePath) where+ toBind v = hostSide v ++ ":" ++ containerSide v++-- | Bind mount a file or directory from the host into the container.+bind :: Bindable p => p -> RevertableProperty+bind p = containerCfg $ "--bind=" ++ toBind p++-- | Read-only mind mount.+bindRo :: Bindable p => p -> RevertableProperty+bindRo p = containerCfg $ "--bind-ro=" ++ toBind p
src/Propellor/Shim.hs view
@@ -8,7 +8,6 @@ import Propellor import Utility.LinuxMkLibs-import Utility.SafeCommand import Utility.FileMode import Utility.FileSystemEncoding
src/Propellor/Spin.hs view
@@ -14,8 +14,7 @@ import Control.Concurrent.Async import qualified Data.ByteString as B import qualified Data.Set as S-import qualified Network.BSD as BSD-import Network.Socket (inet_ntoa)+import Network.Socket (getAddrInfo, defaultHints, AddrInfo(..), AddrInfoFlag(..), SockAddr) import Propellor import Propellor.Protocol@@ -98,17 +97,21 @@ getSshTarget :: HostName -> Host -> IO String getSshTarget target hst | null configips = return target- | otherwise = go =<< tryIO (BSD.getHostByName target)+ | otherwise = go =<< tryIO (dnslookup target) where go (Left e) = useip (show e)- go (Right hostentry) = ifM (anyM matchingconfig (BSD.hostAddresses hostentry))- ( return target- , do- ips <- mapM inet_ntoa (BSD.hostAddresses hostentry)- useip ("DNS " ++ show ips ++ " vs configured " ++ show configips)- )+ go (Right addrinfos) = do+ configaddrinfos <- catMaybes <$> mapM iptoaddr configips+ if any (`elem` configaddrinfos) (map addrAddress addrinfos)+ then return target+ else useip ("DNS lookup did not return any of the expected addresses " ++ show configips) - matchingconfig a = flip elem configips <$> inet_ntoa a+ dnslookup h = getAddrInfo (Just $ defaultHints { addrFlags = [AI_CANONNAME] }) (Just h) Nothing++ -- Convert a string containing an IP address into a SockAddr.+ iptoaddr :: String -> IO (Maybe SockAddr)+ iptoaddr ip = catchDefaultIO Nothing $ headMaybe . map addrAddress+ <$> getAddrInfo (Just $ defaultHints { addrFlags = [AI_NUMERICHOST] }) (Just ip) Nothing useip why = case headMaybe configips of Nothing -> return target
src/Propellor/Ssh.hs view
@@ -1,7 +1,6 @@ module Propellor.Ssh where import Propellor-import Utility.SafeCommand import Utility.UserInfo import System.PosixCompat@@ -23,7 +22,8 @@ let ps = [ Param "-o" , Param ("ControlPath=" ++ socketfile)- , Params "-o ControlMaster=auto -o ControlPersist=yes"+ , Param "-o", Param "ControlMaster=auto"+ , Param "-o", Param "ControlPersist=yes" ] maybe noop (expireold ps socketfile)@@ -38,7 +38,7 @@ then touchFile f else do void $ boolSystem "ssh" $- [ Params "-O stop" ] ++ ps +++ [ Param "-O", Param "stop" ] ++ ps ++ [ Param "localhost" ] nukeFile f tenminutes = 600
+ src/Propellor/Types/Container.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE TypeFamilies #-}++module Propellor.Types.Container where++-- | A value that can be bound between the host and a container.+--+-- For example, a Bound Port is a Port on the container that is bound to+-- a Port on the host.+data Bound v = Bound+ { hostSide :: v+ , containerSide :: v+ }++-- | Create a Bound value, from two different values for the host and+-- container.+--+-- For example, @Port 8080 -<- Port 80@ means that port 8080 on the host+-- is bound to port 80 from the container.+(-<-) :: (hostv ~ v, containerv ~ v) => hostv -> containerv -> Bound v+(-<-) hostv containerv = Bound hostv containerv++-- | Flipped version of -<- with the container value first and host value+-- second.+(->-) :: (containerv ~ v, hostv ~ v) => hostv -> containerv -> Bound v+(->-) containerv hostv = Bound hostv containerv++-- | Create a Bound value, that is the same on both the host and container.+same :: v -> Bound v+same v = Bound v v+
src/Propellor/Types/OS.hs view
@@ -10,6 +10,7 @@ User(..), Group(..), userGroup,+ Port(..), ) where import Network.BSD (HostName)@@ -42,3 +43,6 @@ -- | Makes a Group with the same name as the User. userGroup :: User -> Group userGroup (User u) = Group u++newtype Port = Port Int+ deriving (Eq, Show)
src/Utility/Data.hs view
@@ -5,6 +5,8 @@ - License: BSD-2-clause -} +{-# OPTIONS_GHC -fno-warn-tabs #-}+ module Utility.Data where {- First item in the list that is not Nothing. -}
src/Utility/Directory.hs view
@@ -6,6 +6,7 @@ -} {-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-tabs #-} module Utility.Directory where @@ -18,6 +19,7 @@ import Control.Concurrent import System.IO.Unsafe (unsafeInterleaveIO) import Data.Maybe+import Prelude #ifdef mingw32_HOST_OS import qualified System.Win32 as Win32
src/Utility/Env.hs view
@@ -6,6 +6,7 @@ -} {-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-tabs #-} module Utility.Env where @@ -13,6 +14,7 @@ import Utility.Exception import Control.Applicative import Data.Maybe+import Prelude import qualified System.Environment as E import qualified System.SetEnv #else
src/Utility/Exception.hs view
@@ -6,6 +6,7 @@ -} {-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -fno-warn-tabs #-} module Utility.Exception ( module X,
src/Utility/FileMode.hs view
@@ -22,15 +22,12 @@ {- Applies a conversion function to a file's mode. -} modifyFileMode :: FilePath -> (FileMode -> FileMode) -> IO ()-modifyFileMode f convert = void $ modifyFileMode' f convert-modifyFileMode' :: FilePath -> (FileMode -> FileMode) -> IO FileMode-modifyFileMode' f convert = do+modifyFileMode f convert = do s <- getFileStatus f let old = fileMode s let new = convert old when (new /= old) $ setFileMode f new- return old {- Adds the specified FileModes to the input mode, leaving the rest - unchanged. -}@@ -40,14 +37,6 @@ {- Removes the specified FileModes from the input mode. -} removeModes :: [FileMode] -> FileMode -> FileMode removeModes ms m = m `intersectFileModes` complement (combineModes ms)--{- Runs an action after changing a file's mode, then restores the old mode. -}-withModifiedFileMode :: FilePath -> (FileMode -> FileMode) -> IO a -> IO a-withModifiedFileMode file convert a = bracket setup cleanup go- where- setup = modifyFileMode' file convert- cleanup oldmode = modifyFileMode file (const oldmode)- go _ = a writeModes :: [FileMode] writeModes = [ownerWriteMode, groupWriteMode, otherWriteMode]
src/Utility/FileSystemEncoding.hs view
@@ -6,6 +6,7 @@ -} {-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-tabs #-} module Utility.FileSystemEncoding ( fileEncoding,
src/Utility/LinuxMkLibs.hs view
@@ -7,7 +7,12 @@ module Utility.LinuxMkLibs where -import Control.Applicative+import Utility.PartialPrelude+import Utility.Directory+import Utility.Process+import Utility.Monad+import Utility.Path+ import Data.Maybe import System.Directory import System.FilePath@@ -15,12 +20,8 @@ import System.Posix.Files import Data.Char import Control.Monad.IfElse--import Utility.PartialPrelude-import Utility.Directory-import Utility.Process-import Utility.Monad-import Utility.Path+import Control.Applicative+import Prelude {- Installs a library. If the library is a symlink to another file, - install the file it links to, and update the symlink to be relative. -}
src/Utility/Misc.hs view
@@ -6,23 +6,25 @@ -} {-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-tabs #-} module Utility.Misc where +import Utility.FileSystemEncoding+import Utility.Monad+ import System.IO import Control.Monad import Foreign import Data.Char import Data.List-import Control.Applicative import System.Exit #ifndef mingw32_HOST_OS import System.Posix.Process (getAnyProcessStatus) import Utility.Exception #endif--import Utility.FileSystemEncoding-import Utility.Monad+import Control.Applicative+import Prelude {- A version of hgetContents that is not lazy. Ensures file is - all read before it gets closed. -}
src/Utility/Monad.hs view
@@ -5,6 +5,8 @@ - License: BSD-2-clause -} +{-# OPTIONS_GHC -fno-warn-tabs #-}+ module Utility.Monad where import Data.Maybe
src/Utility/PartialPrelude.hs view
@@ -5,6 +5,8 @@ - them being accidentially used. -} +{-# OPTIONS_GHC -fno-warn-tabs #-}+ module Utility.PartialPrelude where import qualified Data.Maybe
src/Utility/Path.hs view
@@ -6,6 +6,7 @@ -} {-# LANGUAGE PackageImports, CPP #-}+{-# OPTIONS_GHC -fno-warn-tabs #-} module Utility.Path where @@ -16,6 +17,7 @@ import Data.Maybe import Data.Char import Control.Applicative+import Prelude #ifdef mingw32_HOST_OS import qualified System.FilePath.Posix as Posix
src/Utility/PosixFiles.hs view
@@ -8,6 +8,7 @@ -} {-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-tabs #-} module Utility.PosixFiles ( module X,
src/Utility/Process.hs view
@@ -1,12 +1,13 @@ {- System.Process enhancements, including additional ways of running - processes, and logging. -- - Copyright 2012 Joey Hess <id@joeyh.name>+ - Copyright 2012-2015 Joey Hess <id@joeyh.name> - - License: BSD-2-clause -} {-# LANGUAGE CPP, Rank2Types #-}+{-# OPTIONS_GHC -fno-warn-tabs #-} module Utility.Process ( module X,@@ -54,6 +55,7 @@ import Control.Applicative #endif import Data.Maybe+import Prelude import Utility.Misc import Utility.Exception@@ -63,8 +65,8 @@ data StdHandle = StdinHandle | StdoutHandle | StderrHandle deriving (Eq) -{- Normally, when reading from a process, it does not need to be fed any- - standard input. -}+-- | Normally, when reading from a process, it does not need to be fed any+-- standard input. readProcess :: FilePath -> [String] -> IO String readProcess cmd args = readProcessEnv cmd args Nothing @@ -82,9 +84,8 @@ hClose h return output -{- Runs an action to write to a process on its stdin, - - returns its output, and also allows specifying the environment.- -}+-- | Runs an action to write to a process on its stdin, +-- returns its output, and also allows specifying the environment. writeReadProcessEnv :: FilePath -> [String]@@ -124,8 +125,8 @@ , env = environ } -{- Waits for a ProcessHandle, and throws an IOError if the process- - did not exit successfully. -}+-- | Waits for a ProcessHandle, and throws an IOError if the process+-- did not exit successfully. forceSuccessProcess :: CreateProcess -> ProcessHandle -> IO () forceSuccessProcess p pid = do code <- waitForProcess pid@@ -133,10 +134,10 @@ ExitSuccess -> return () ExitFailure n -> fail $ showCmd p ++ " exited " ++ show n -{- Waits for a ProcessHandle and returns True if it exited successfully.- - Note that using this with createProcessChecked will throw away- - the Bool, and is only useful to ignore the exit code of a process,- - while still waiting for it. -}+-- | Waits for a ProcessHandle and returns True if it exited successfully.+-- Note that using this with createProcessChecked will throw away+-- the Bool, and is only useful to ignore the exit code of a process,+-- while still waiting for it. -} checkSuccessProcess :: ProcessHandle -> IO Bool checkSuccessProcess pid = do code <- waitForProcess pid@@ -147,13 +148,13 @@ void $ waitForProcess pid return True -{- Runs createProcess, then an action on its handles, and then- - forceSuccessProcess. -}+-- | Runs createProcess, then an action on its handles, and then+-- forceSuccessProcess. createProcessSuccess :: CreateProcessRunner createProcessSuccess p a = createProcessChecked (forceSuccessProcess p) p a -{- Runs createProcess, then an action on its handles, and then- - a checker action on its exit code, which must wait for the process. -}+-- | Runs createProcess, then an action on its handles, and then+-- a checker action on its exit code, which must wait for the process. createProcessChecked :: (ProcessHandle -> IO b) -> CreateProcessRunner createProcessChecked checker p a = do t@(_, _, _, pid) <- createProcess p@@ -161,14 +162,14 @@ _ <- checker pid either E.throw return r -{- Leaves the process running, suitable for lazy streaming.- - Note: Zombies will result, and must be waited on. -}+-- | Leaves the process running, suitable for lazy streaming.+-- Note: Zombies will result, and must be waited on. createBackgroundProcess :: CreateProcessRunner createBackgroundProcess p a = a =<< createProcess p -{- Runs a process, optionally feeding it some input, and- - returns a transcript combining its stdout and stderr, and- - whether it succeeded or failed. -}+-- | Runs a process, optionally feeding it some input, and+-- returns a transcript combining its stdout and stderr, and+-- whether it succeeded or failed. processTranscript :: String -> [String] -> (Maybe String) -> IO (String, Bool) processTranscript cmd opts input = processTranscript' cmd opts Nothing input @@ -232,9 +233,9 @@ hClose inh writeinput Nothing _ = return () -{- Runs a CreateProcessRunner, on a CreateProcess structure, that- - is adjusted to pipe only from/to a single StdHandle, and passes- - the resulting Handle to an action. -}+-- | Runs a CreateProcessRunner, on a CreateProcess structure, that+-- is adjusted to pipe only from/to a single StdHandle, and passes+-- the resulting Handle to an action. withHandle :: StdHandle -> CreateProcessRunner@@ -256,7 +257,7 @@ | h == StderrHandle = (stderrHandle, base { std_err = CreatePipe }) -{- Like withHandle, but passes (stdin, stdout) handles to the action. -}+-- | Like withHandle, but passes (stdin, stdout) handles to the action. withIOHandles :: CreateProcessRunner -> CreateProcess@@ -270,7 +271,7 @@ , std_err = Inherit } -{- Like withHandle, but passes (stdout, stderr) handles to the action. -}+-- | Like withHandle, but passes (stdout, stderr) handles to the action. withOEHandles :: CreateProcessRunner -> CreateProcess@@ -284,8 +285,8 @@ , std_err = CreatePipe } -{- Forces the CreateProcessRunner to run quietly;- - both stdout and stderr are discarded. -}+-- | Forces the CreateProcessRunner to run quietly;+-- both stdout and stderr are discarded. withQuietOutput :: CreateProcessRunner -> CreateProcess@@ -297,8 +298,8 @@ } creator p' $ const $ return () -{- Stdout and stderr are discarded, while the process is fed stdin- - from the handle. -}+-- | Stdout and stderr are discarded, while the process is fed stdin+-- from the handle. feedWithQuietOutput :: CreateProcessRunner -> CreateProcess@@ -319,11 +320,11 @@ devNull = "NUL" #endif -{- Extract a desired handle from createProcess's tuple.- - These partial functions are safe as long as createProcess is run- - with appropriate parameters to set up the desired handle.- - Get it wrong and the runtime crash will always happen, so should be- - easily noticed. -}+-- | Extract a desired handle from createProcess's tuple.+-- These partial functions are safe as long as createProcess is run+-- with appropriate parameters to set up the desired handle.+-- Get it wrong and the runtime crash will always happen, so should be+-- easily noticed. type HandleExtractor = (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -> Handle stdinHandle :: HandleExtractor stdinHandle (Just h, _, _, _) = h@@ -344,7 +345,7 @@ processHandle :: (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -> ProcessHandle processHandle (_, _, _, pid) = pid -{- Debugging trace for a CreateProcess. -}+-- | Debugging trace for a CreateProcess. debugProcess :: CreateProcess -> IO () debugProcess p = do debugM "Utility.Process" $ unwords@@ -360,15 +361,15 @@ piped Inherit = False piped _ = True -{- Shows the command that a CreateProcess will run. -}+-- | Shows the command that a CreateProcess will run. showCmd :: CreateProcess -> String showCmd = go . cmdspec where go (ShellCommand s) = s go (RawCommand c ps) = c ++ " " ++ show ps -{- Starts an interactive process. Unlike runInteractiveProcess in- - System.Process, stderr is inherited. -}+-- | Starts an interactive process. Unlike runInteractiveProcess in+-- System.Process, stderr is inherited. startInteractiveProcess :: FilePath -> [String]@@ -384,7 +385,8 @@ (Just from, Just to, _, pid) <- createProcess p return (pid, to, from) -{- Wrapper around System.Process function that does debug logging. -}+-- | Wrapper around 'System.Process.createProcess' from System.Process,+-- that does debug logging. createProcess :: CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) createProcess p = do debugProcess p
src/Utility/QuickCheck.hs view
@@ -19,6 +19,7 @@ import qualified Data.Map as M import qualified Data.Set as S import Control.Applicative+import Prelude instance (Arbitrary k, Arbitrary v, Eq k, Ord k) => Arbitrary (M.Map k v) where arbitrary = M.fromList <$> arbitrary
src/Utility/SafeCommand.hs view
@@ -1,84 +1,93 @@ {- safely running shell commands -- - Copyright 2010-2013 Joey Hess <id@joeyh.name>+ - Copyright 2010-2015 Joey Hess <id@joeyh.name> - - License: BSD-2-clause -} +{-# OPTIONS_GHC -fno-warn-tabs #-}+ module Utility.SafeCommand where import System.Exit import Utility.Process import Data.String.Utils-import Control.Applicative import System.FilePath import Data.Char+import Control.Applicative+import Prelude -{- A type for parameters passed to a shell command. A command can- - be passed either some Params (multiple parameters can be included,- - whitespace-separated, or a single Param (for when parameters contain- - whitespace), or a File.- -}-data CommandParam = Params String | Param String | File FilePath+-- | Parameters that can be passed to a shell command.+data CommandParam+ = Param String -- ^ A parameter+ | File FilePath -- ^ The name of a file deriving (Eq, Show, Ord) -{- Used to pass a list of CommandParams to a function that runs- - a command and expects Strings. -}+-- | Used to pass a list of CommandParams to a function that runs+-- a command and expects Strings. -} toCommand :: [CommandParam] -> [String]-toCommand = concatMap unwrap+toCommand = map unwrap where- unwrap (Param s) = [s]- unwrap (Params s) = filter (not . null) (split " " s)+ unwrap (Param s) = s -- Files that start with a non-alphanumeric that is not a path -- separator are modified to avoid the command interpreting them as -- options or other special constructs. unwrap (File s@(h:_))- | isAlphaNum h || h `elem` pathseps = [s]- | otherwise = ["./" ++ s]- unwrap (File s) = [s]+ | isAlphaNum h || h `elem` pathseps = s+ | otherwise = "./" ++ s+ unwrap (File s) = s -- '/' is explicitly included because it's an alternative -- path separator on Windows. pathseps = pathSeparator:"./" -{- Run a system command, and returns True or False- - if it succeeded or failed.- -}+-- | Run a system command, and returns True or False if it succeeded or failed.+--+-- This and other command running functions in this module log the commands+-- run at debug level, using System.Log.Logger. boolSystem :: FilePath -> [CommandParam] -> IO Bool-boolSystem command params = boolSystemEnv command params Nothing+boolSystem command params = boolSystem' command params id -boolSystemEnv :: FilePath -> [CommandParam] -> Maybe [(String, String)] -> IO Bool-boolSystemEnv command params environ = dispatch <$> safeSystemEnv command params environ+boolSystem' :: FilePath -> [CommandParam] -> (CreateProcess -> CreateProcess) -> IO Bool+boolSystem' command params mkprocess = dispatch <$> safeSystem' command params mkprocess where dispatch ExitSuccess = True dispatch _ = False -{- Runs a system command, returning the exit status. -}+boolSystemEnv :: FilePath -> [CommandParam] -> Maybe [(String, String)] -> IO Bool+boolSystemEnv command params environ = boolSystem' command params $+ \p -> p { env = environ }++-- | Runs a system command, returning the exit status. safeSystem :: FilePath -> [CommandParam] -> IO ExitCode-safeSystem command params = safeSystemEnv command params Nothing+safeSystem command params = safeSystem' command params id -safeSystemEnv :: FilePath -> [CommandParam] -> Maybe [(String, String)] -> IO ExitCode-safeSystemEnv command params environ = do- (_, _, _, pid) <- createProcess (proc command $ toCommand params)- { env = environ }+safeSystem' :: FilePath -> [CommandParam] -> (CreateProcess -> CreateProcess) -> IO ExitCode+safeSystem' command params mkprocess = do+ (_, _, _, pid) <- createProcess p waitForProcess pid+ where+ p = mkprocess $ proc command (toCommand params) -{- Wraps a shell command line inside sh -c, allowing it to be run in a- - login shell that may not support POSIX shell, eg csh. -}+safeSystemEnv :: FilePath -> [CommandParam] -> Maybe [(String, String)] -> IO ExitCode+safeSystemEnv command params environ = safeSystem' command params $ + \p -> p { env = environ }++-- | Wraps a shell command line inside sh -c, allowing it to be run in a+-- login shell that may not support POSIX shell, eg csh. shellWrap :: String -> String shellWrap cmdline = "sh -c " ++ shellEscape cmdline -{- Escapes a filename or other parameter to be safely able to be exposed to- - the shell.- -- - This method works for POSIX shells, as well as other shells like csh.- -}+-- | Escapes a filename or other parameter to be safely able to be exposed to+-- the shell.+--+-- This method works for POSIX shells, as well as other shells like csh. shellEscape :: String -> String shellEscape f = "'" ++ escaped ++ "'" where -- replace ' with '"'"' escaped = join "'\"'\"'" $ split "'" f -{- Unescapes a set of shellEscaped words or filenames. -}+-- | Unescapes a set of shellEscaped words or filenames. shellUnEscape :: String -> [String] shellUnEscape [] = [] shellUnEscape s = word : shellUnEscape rest@@ -95,19 +104,19 @@ | c == q = findword w cs | otherwise = inquote q (w++[c]) cs -{- For quickcheck. -}+-- | For quickcheck. prop_idempotent_shellEscape :: String -> Bool prop_idempotent_shellEscape s = [s] == (shellUnEscape . shellEscape) s prop_idempotent_shellEscape_multiword :: [String] -> Bool prop_idempotent_shellEscape_multiword s = s == (shellUnEscape . unwords . map shellEscape) s -{- Segments a list of filenames into groups that are all below the maximum- - command-line length limit. -}+-- | Segments a list of filenames into groups that are all below the maximum+-- command-line length limit. segmentXargsOrdered :: [FilePath] -> [[FilePath]] segmentXargsOrdered = reverse . map reverse . segmentXargsUnordered -{- Not preserving data is a little faster, and streams better when- - there are a great many filesnames. -}+-- | Not preserving order is a little faster, and streams better when+-- there are a great many filenames. segmentXargsUnordered :: [FilePath] -> [[FilePath]] segmentXargsUnordered l = go l [] 0 [] where
src/Utility/Scheduled.hs view
@@ -32,7 +32,6 @@ import Utility.PartialPrelude import Utility.Misc -import Control.Applicative import Data.List import Data.Time.Clock import Data.Time.LocalTime@@ -41,6 +40,8 @@ import Data.Time.Calendar.OrdinalDate import Data.Tuple.Utils import Data.Char+import Control.Applicative+import Prelude {- Some sort of scheduled event. -} data Schedule = Schedule Recurrance ScheduledTime
src/Utility/Tmp.hs view
@@ -6,6 +6,7 @@ -} {-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-tabs #-} module Utility.Tmp where
src/Utility/UserInfo.hs view
@@ -6,6 +6,7 @@ -} {-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-tabs #-} module Utility.UserInfo ( myHomeDir,@@ -13,12 +14,13 @@ myUserGecos, ) where +import Utility.Env+ import System.PosixCompat #ifndef mingw32_HOST_OS import Control.Applicative #endif--import Utility.Env+import Prelude {- Current user's home directory. -