From c7830f4e669735bf46945592b315e7e367129888 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sat, 12 Apr 2014 22:36:36 -0400 Subject: propellor spin --- Propellor/Property/Cron.hs | 9 +++++- Propellor/Property/Obnam.hs | 77 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 Propellor/Property/Obnam.hs (limited to 'Propellor/Property') diff --git a/Propellor/Property/Cron.hs b/Propellor/Property/Cron.hs index fa6019ea..2fa9c87e 100644 --- a/Propellor/Property/Cron.hs +++ b/Propellor/Property/Cron.hs @@ -4,13 +4,15 @@ import Propellor import qualified Propellor.Property.File as File import qualified Propellor.Property.Apt as Apt +import Data.Char + type CronTimes = String -- | Installs a cron job, run as a specificed user, in a particular --directory. Note that the Desc must be unique, as it is used for the --cron.d/ filename. job :: Desc -> CronTimes -> UserName -> FilePath -> String -> Property -job desc times user cddir command = ("/etc/cron.d/" ++ desc) `File.hasContent` +job desc times user cddir command = cronjobfile `File.hasContent` [ "# Generated by propellor" , "" , "SHELL=/bin/sh" @@ -20,6 +22,11 @@ job desc times user cddir command = ("/etc/cron.d/" ++ desc) `File.hasContent` ] `requires` Apt.serviceInstalledRunning "cron" `describe` ("cronned " ++ desc) + where + cronjobfile = "/etc/cron.d/" ++ map sanitize desc + sanitize c + | isAlphaNum c = c + | otherwise = '_' -- | Installs a cron job, and runs it niced and ioniced. niceJob :: Desc -> CronTimes -> UserName -> FilePath -> String -> Property diff --git a/Propellor/Property/Obnam.hs b/Propellor/Property/Obnam.hs new file mode 100644 index 00000000..ebdcb9dd --- /dev/null +++ b/Propellor/Property/Obnam.hs @@ -0,0 +1,77 @@ +module Propellor.Property.Obnam where + +import Propellor +import qualified Propellor.Property.Apt as Apt +import qualified Propellor.Property.Cron as Cron +import Utility.SafeCommand + +installed :: Property +installed = Apt.installed ["obnam"] + +type ObnamParam = String + +-- | Installs a cron job that causes a given directory to be backed +-- up, by running obnam with some parameters. +-- +-- If the directory does not exist, or exists but is completely empty, +-- this Property will immediately restore it from an existing backup. +-- +-- So, this property can be used to deploy a directory of content +-- to a host, while also ensuring any changes made to it get backed up. +-- And since Obnam encrypts, just make this property depend on a gpg +-- key, and tell obnam to use the key, and your data will be backed +-- up securely. For example: +-- +-- > & Obnam.backup "/srv/git" "33 3 * * *" +-- > [ "--repository=2318@usw-s002.rsync.net:mygitrepos.obnam" +-- > , "--encrypt-with=1B169BE1" +-- > ] +-- > `requires` Gpg.keyImported "1B169BE1" "root" +-- > `requires` Ssh.keyImported SshRsa "root" +-- +-- How awesome is that? +backup :: FilePath -> Cron.CronTimes -> [ObnamParam] -> Property +backup dir crontimes params = cronjob `describe` desc + `requires` restored dir params + `requires` installed + where + desc = dir ++ " backed up by obnam" + cronjob = Cron.niceJob ("obnam_backup" ++ dir) crontimes "root" "/" $ + unwords $ + [ "obnam" + , "backup" + , shellEscape dir + ] ++ map shellEscape params + +-- | Restores a directory from an obnam backup. +-- +-- Only does anything if the directory does not exist, or exists, +-- but is completely empty. +-- +-- The restore is performed atomically; restoring to a temp directory +-- and then moving it to the directory. +restored :: FilePath -> [ObnamParam] -> Property +restored dir params = Property (dir ++ " restored by obnam") go + `requires` installed + where + go = ifM (liftIO needsRestore) + ( liftIO restore + , noChange + ) + + needsRestore = null <$> catchDefaultIO [] (dirContents dir) + + restore = withTmpDirIn (takeDirectory dir) "obnam-restore" $ \tmpdir -> do + ok <- boolSystem "obnam" $ + [ Param "restore" + , Param "--to" + , Param tmpdir + ] ++ map Param params + let restoreddir = tmpdir ++ "/" ++ dir + ifM (pure ok <&&> doesDirectoryExist restoreddir) + ( do + void $ tryIO $ removeDirectory dir + renameDirectory restoreddir dir + return MadeChange + , return FailedChange + ) -- cgit v1.2.3