module Propellor.Property.Cron where import Propellor.Base import qualified Propellor.Property.File as File import qualified Propellor.Property.Apt as Apt import Propellor.Bootstrap import Utility.FileMode import Data.Char -- | When to run a cron job. -- -- The Daily, Monthly, and Weekly options allow the cron job to be run -- by anacron, which is useful for non-servers. data Times = Times String -- ^ formatted as in crontab(5) | Daily | Weekly | Monthly -- | Installs a cron job, that will run as a specified user in a particular -- directory. Note that the Desc must be unique, as it is used for the -- cron job filename. -- -- Only one instance of the cron job is allowed to run at a time, no matter -- how long it runs. This is accomplished using flock locking of the cron -- job file. -- -- The cron job's output will only be emailed if it exits nonzero. job :: Desc -> Times -> User -> FilePath -> String -> Property DebianLike job desc times (User u) cddir command = combineProperties ("cronned " ++ desc) $ props & Apt.serviceInstalledRunning "cron" & Apt.installed ["util-linux", "moreutils"] & cronjobfile `File.hasContent` [ case times of Times _ -> "" _ -> "#!/bin/sh\nset -e" , "# Generated by propellor" , "" , "SHELL=/bin/sh" , "PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin" , "" , case times of Times t -> t ++ "\t" ++ u ++ "\tchronic " ++ shellEscape scriptfile _ -> case u of "root" -> "chronic " ++ shellEscape scriptfile _ -> "chronic su " ++ u ++ " -c " ++ shellEscape scriptfile ] & case times of Times _ -> doNothing _ -> cronjobfile `File.mode` combineModes (readModes ++ executeModes) -- Use a separate script because it makes the cron job name -- prettier in emails, and also allows running the job manually. & scriptfile `File.hasContent` [ "#!/bin/sh" , "# Generated by propellor" , "set -e" , "flock -n " ++ shellEscape cronjobfile ++ " sh -c " ++ shellEscape cmdline ] & scriptfile `File.mode` combineModes (readModes ++ executeModes) where cmdline = "cd " ++ cddir ++ " && ( " ++ command ++ " )" cronjobfile = "/etc" cronjobdir name cronjobdir = case times of Times _ -> "cron.d" Daily -> "cron.daily" Weekly -> "cron.weekly" Monthly -> "cron.monthly" scriptfile = "/usr/local/bin/" ++ name ++ "_cronjob" name = map sanitize desc sanitize c | isAlphaNum c = c | otherwise = '_' -- | Installs a cron job, and runs it niced and ioniced. niceJob :: Desc -> Times -> User -> FilePath -> String -> Property DebianLike niceJob desc times user cddir command = job desc times user cddir ("nice ionice -c 3 sh -c " ++ shellEscape command) -- | Installs a cron job to run propellor. runPropellor :: Times -> Property UnixLike runPropellor times = withOS "propellor cron job" $ \w o -> do bootstrapper <- getBootstrapper ensureProperty w $ niceJob "propellor" times (User "root") localdir (bootstrapPropellorCommand bootstrapper o ++ "; ./propellor")