    -- | Support for the Obnam backup tool <http://obnam.org/>
    --
    -- This module is deprecated because Obnam has been retired by its
    -- author.

    module Propellor.Property.Obnam {-# DEPRECATED "Obnam has been retired; time to transition to something else" #-} where

    import Propellor.Base
    import qualified Propellor.Property.Apt as Apt
    import qualified Propellor.Property.Cron as Cron
    import qualified Propellor.Property.Gpg as Gpg

    import Data.List

    type ObnamParam = String

    -- | An obnam repository can be used by multiple clients. Obnam uses
    -- locking to allow only one client to write at a time. Since stale lock
    -- files can prevent backups from happening, it's more robust, if you know
    -- a repository has only one client, to force the lock before starting a
    -- backup. Using OnlyClient allows propellor to do so when running obnam.
    data NumClients = OnlyClient | MultipleClients
    	deriving (NumClients -> NumClients -> Bool
    (NumClients -> NumClients -> Bool)
    -> (NumClients -> NumClients -> Bool) -> Eq NumClients
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    $c== :: NumClients -> NumClients -> Bool
    == :: NumClients -> NumClients -> Bool
    $c/= :: NumClients -> NumClients -> Bool
    /= :: NumClients -> NumClients -> Bool
    Eq)

    -- | 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.
    -- For example:
    --
    -- >	& Obnam.backup "/srv/git" "33 3 * * *"
    -- >		[ "--repository=sftp://2318@usw-s002.rsync.net/~/mygitrepos.obnam"
    -- >		] Obnam.OnlyClient
    -- >		`requires` Ssh.keyImported SshEd25519 "root" (Context hostname)
    --
    -- How awesome is that?
    --
    -- Note that this property does not make obnam encrypt the backup
    -- repository.
    --
    -- Since obnam uses a fair amount of system resources, only one obnam
    -- backup job will be run at a time. Other jobs will wait their turns to
    -- run.
    backup :: FilePath -> Cron.Times -> [ObnamParam] -> NumClients -> Property DebianLike
    backup :: FilePath
    -> Times -> [FilePath] -> NumClients -> Property DebianLike
    backup FilePath
    dir Times
    crontimes [FilePath]
    params NumClients
    numclients =
    	FilePath
    -> Times -> [FilePath] -> NumClients -> Property DebianLike
    backup' FilePath
    dir Times
    crontimes [FilePath]
    params NumClients
    numclients
    		Property DebianLike
    -> Property DebianLike
    -> CombinedType (Property DebianLike) (Property DebianLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires` FilePath -> [FilePath] -> Property DebianLike
    restored FilePath
    dir [FilePath]
    params

    -- | Like backup, but the specified gpg key id is used to encrypt
    -- the repository.
    --
    -- The gpg secret key will be automatically imported
    -- into root's keyring using Propellor.Property.Gpg.keyImported
    backupEncrypted :: FilePath -> Cron.Times -> [ObnamParam] -> NumClients -> Gpg.GpgKeyId -> Property (HasInfo + DebianLike)
    backupEncrypted :: FilePath
    -> Times
    -> [FilePath]
    -> NumClients
    -> GpgKeyId
    -> Property (HasInfo + DebianLike)
    backupEncrypted FilePath
    dir Times
    crontimes [FilePath]
    params NumClients
    numclients GpgKeyId
    keyid =
    	FilePath
    -> Times -> [FilePath] -> NumClients -> Property DebianLike
    backup FilePath
    dir Times
    crontimes [FilePath]
    params' NumClients
    numclients
    		Property DebianLike
    -> Property
         (MetaTypes
            '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish])
    -> CombinedType
         (Property DebianLike)
         (Property
            (MetaTypes
               '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish]))
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires` GpgKeyId -> User -> Property (HasInfo + DebianLike)
    Gpg.keyImported GpgKeyId
    keyid (FilePath -> User
    User FilePath
    "root")
      where
    	params' :: [FilePath]
    params' = (FilePath
    "--encrypt-with=" FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ GpgKeyId -> FilePath
    Gpg.getGpgKeyId GpgKeyId
    keyid) FilePath -> [FilePath] -> [FilePath]
    forall a. a -> [a] -> [a]
    : [FilePath]
    params

    -- | Does a backup, but does not automatically restore.
    backup' :: FilePath -> Cron.Times -> [ObnamParam] -> NumClients -> Property DebianLike
    backup' :: FilePath
    -> Times -> [FilePath] -> NumClients -> Property DebianLike
    backup' FilePath
    dir Times
    crontimes [FilePath]
    params NumClients
    numclients = Property DebianLike
    cronjob Property DebianLike -> FilePath -> Property DebianLike
    forall p. IsProp p => p -> FilePath -> p
    `describe` FilePath
    desc
      where
    	desc :: FilePath
    desc = FilePath
    dir FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath
    " backed up by obnam"
    	cronjob :: Property DebianLike
    cronjob = FilePath
    -> Times -> User -> FilePath -> FilePath -> Property DebianLike
    Cron.niceJob (FilePath
    "obnam_backup" FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath
    dir) Times
    crontimes (FilePath -> User
    User FilePath
    "root") FilePath
    "/" (FilePath -> Property DebianLike)
    -> FilePath -> Property DebianLike
    forall a b. (a -> b) -> a -> b
    $
    		FilePath
    "flock " FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath -> FilePath
    shellEscape FilePath
    lockfile FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath
    " sh -c " FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath -> FilePath
    shellEscape FilePath
    cmdline
    	lockfile :: FilePath
    lockfile = FilePath
    "/var/lock/propellor-obnam.lock"
    	cmdline :: FilePath
    cmdline = [FilePath] -> FilePath
    unwords ([FilePath] -> FilePath) -> [FilePath] -> FilePath
    forall a b. (a -> b) -> a -> b
    $ [Maybe FilePath] -> [FilePath]
    forall a. [Maybe a] -> [a]
    catMaybes
    		[ if NumClients
    numclients NumClients -> NumClients -> Bool
    forall a. Eq a => a -> a -> Bool
    == NumClients
    OnlyClient
    			-- forcelock fails if repo does not exist yet
    			then FilePath -> Maybe FilePath
    forall a. a -> Maybe a
    Just (FilePath -> Maybe FilePath) -> FilePath -> Maybe FilePath
    forall a b. (a -> b) -> a -> b
    $ FilePath
    forcelockcmd FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath
    " 2>/dev/null ;"
    			else Maybe FilePath
    forall a. Maybe a
    Nothing
    		, FilePath -> Maybe FilePath
    forall a. a -> Maybe a
    Just FilePath
    backupcmd
    		, if (FilePath -> Bool) -> [FilePath] -> Bool
    forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
    any FilePath -> Bool
    isKeepParam [FilePath]
    params
    			then FilePath -> Maybe FilePath
    forall a. a -> Maybe a
    Just (FilePath -> Maybe FilePath) -> FilePath -> Maybe FilePath
    forall a b. (a -> b) -> a -> b
    $ FilePath
    "&& " FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath
    forgetcmd
    			else Maybe FilePath
    forall a. Maybe a
    Nothing
    		]
    	forcelockcmd :: FilePath
    forcelockcmd = [FilePath] -> FilePath
    unwords ([FilePath] -> FilePath) -> [FilePath] -> FilePath
    forall a b. (a -> b) -> a -> b
    $
    		[ FilePath
    "obnam"
    		, FilePath
    "force-lock"
    		] [FilePath] -> [FilePath] -> [FilePath]
    forall a. [a] -> [a] -> [a]
    ++ (FilePath -> FilePath) -> [FilePath] -> [FilePath]
    forall a b. (a -> b) -> [a] -> [b]
    map FilePath -> FilePath
    shellEscape [FilePath]
    params
    	backupcmd :: FilePath
    backupcmd = [FilePath] -> FilePath
    unwords ([FilePath] -> FilePath) -> [FilePath] -> FilePath
    forall a b. (a -> b) -> a -> b
    $
    		[ FilePath
    "obnam"
    		, FilePath
    "backup"
    		, FilePath -> FilePath
    shellEscape FilePath
    dir
    		] [FilePath] -> [FilePath] -> [FilePath]
    forall a. [a] -> [a] -> [a]
    ++ (FilePath -> FilePath) -> [FilePath] -> [FilePath]
    forall a b. (a -> b) -> [a] -> [b]
    map FilePath -> FilePath
    shellEscape [FilePath]
    params
    	forgetcmd :: FilePath
    forgetcmd = [FilePath] -> FilePath
    unwords ([FilePath] -> FilePath) -> [FilePath] -> FilePath
    forall a b. (a -> b) -> a -> b
    $
    		[ FilePath
    "obnam"
    		, FilePath
    "forget"
    		] [FilePath] -> [FilePath] -> [FilePath]
    forall a. [a] -> [a] -> [a]
    ++ (FilePath -> FilePath) -> [FilePath] -> [FilePath]
    forall a b. (a -> b) -> [a] -> [b]
    map FilePath -> FilePath
    shellEscape [FilePath]
    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 DebianLike
    restored :: FilePath -> [FilePath] -> Property DebianLike
    restored FilePath
    dir [FilePath]
    params = Property DebianLike
    go Property DebianLike
    -> Property DebianLike
    -> CombinedType (Property DebianLike) (Property DebianLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires` Property DebianLike
    installed
      where
    	desc :: FilePath
    desc = FilePath
    dir FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath
    " restored by obnam"
    	go :: Property DebianLike
    	go :: Property DebianLike
    go = FilePath -> Propellor Result -> Property DebianLike
    forall {k} (metatypes :: k).
    SingI metatypes =>
    FilePath -> Propellor Result -> Property (MetaTypes metatypes)
    property FilePath
    desc (Propellor Result -> Property DebianLike)
    -> Propellor Result -> Property DebianLike
    forall a b. (a -> b) -> a -> b
    $ Propellor Bool
    -> (Propellor Result, Propellor Result) -> Propellor Result
    forall (m :: * -> *) a. Monad m => m Bool -> (m a, m a) -> m a
    ifM (IO Bool -> Propellor Bool
    forall a. IO a -> Propellor a
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
    liftIO IO Bool
    needsRestore)
    		( do
    			FilePath -> Propellor ()
    forall (m :: * -> *). MonadIO m => FilePath -> m ()
    warningMessage (FilePath -> Propellor ()) -> FilePath -> Propellor ()
    forall a b. (a -> b) -> a -> b
    $ FilePath
    dir FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath
    " is empty/missing; restoring from backup ..."
    			IO Result -> Propellor Result
    forall a. IO a -> Propellor a
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
    liftIO IO Result
    restore
    		, Propellor Result
    noChange
    		)

    	needsRestore :: IO Bool
    needsRestore = FilePath -> IO Bool
    isUnpopulated FilePath
    dir

    	restore :: IO Result
    restore = FilePath -> FilePath -> (FilePath -> IO Result) -> IO Result
    forall (m :: * -> *) a.
    (MonadMask m, MonadIO m) =>
    FilePath -> FilePath -> (FilePath -> m a) -> m a
    withTmpDirIn (FilePath -> FilePath
    takeDirectory FilePath
    dir) FilePath
    "obnam-restore" ((FilePath -> IO Result) -> IO Result)
    -> (FilePath -> IO Result) -> IO Result
    forall a b. (a -> b) -> a -> b
    $ \FilePath
    tmpdir -> do
    		ok <- FilePath -> [CommandParam] -> IO Bool
    boolSystem FilePath
    "obnam" ([CommandParam] -> IO Bool) -> [CommandParam] -> IO Bool
    forall a b. (a -> b) -> a -> b
    $
    			[ FilePath -> CommandParam
    Param FilePath
    "restore"
    			, FilePath -> CommandParam
    Param FilePath
    "--to"
    			, FilePath -> CommandParam
    Param FilePath
    tmpdir
    			] [CommandParam] -> [CommandParam] -> [CommandParam]
    forall a. [a] -> [a] -> [a]
    ++ (FilePath -> CommandParam) -> [FilePath] -> [CommandParam]
    forall a b. (a -> b) -> [a] -> [b]
    map FilePath -> CommandParam
    Param [FilePath]
    params
    		let restoreddir = FilePath
    tmpdir FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath
    "/" FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath
    dir
    		ifM (pure ok <&&> doesDirectoryExist restoreddir)
    			( do
    				void $ tryIO $ removeDirectory dir
    				renameDirectory restoreddir dir
    				return MadeChange
    			, return FailedChange
    			)

    -- | Policy for backup generations to keep. For example, KeepDays 30 will
    -- keep the latest backup for each day when a backup was made, and keep the
    -- last 30 such backups. When multiple KeepPolicies are combined together,
    -- backups meeting any policy are kept. See obnam's man page for details.
    data KeepPolicy
    	= KeepHours Int
    	| KeepDays Int
    	| KeepWeeks Int
    	| KeepMonths Int
    	| KeepYears Int

    -- | Constructs an ObnamParam that specifies which old backup generations
    -- to keep. By default, all generations are kept. However, when this parameter
    -- is passed to the `backup` or `backupEncrypted` properties, they will run
    -- obnam forget to clean out generations not specified here.
    keepParam :: [KeepPolicy] -> ObnamParam
    keepParam :: [KeepPolicy] -> FilePath
    keepParam [KeepPolicy]
    ps = FilePath
    "--keep=" FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath -> [FilePath] -> FilePath
    forall a. [a] -> [[a]] -> [a]
    intercalate FilePath
    "," ((KeepPolicy -> FilePath) -> [KeepPolicy] -> [FilePath]
    forall a b. (a -> b) -> [a] -> [b]
    map KeepPolicy -> FilePath
    go [KeepPolicy]
    ps)
      where
    	go :: KeepPolicy -> FilePath
    go (KeepHours Int
    n) = Int -> Char -> FilePath
    forall {t}. ConfigurableValue t => t -> Char -> FilePath
    mk Int
    n Char
    'h'
    	go (KeepDays Int
    n) = Int -> Char -> FilePath
    forall {t}. ConfigurableValue t => t -> Char -> FilePath
    mk Int
    n Char
    'd'
    	go (KeepWeeks Int
    n) = Int -> Char -> FilePath
    forall {t}. ConfigurableValue t => t -> Char -> FilePath
    mk Int
    n Char
    'w'
    	go (KeepMonths Int
    n) = Int -> Char -> FilePath
    forall {t}. ConfigurableValue t => t -> Char -> FilePath
    mk Int
    n Char
    'm'
    	go (KeepYears Int
    n) = Int -> Char -> FilePath
    forall {t}. ConfigurableValue t => t -> Char -> FilePath
    mk Int
    n Char
    'y'
    	mk :: t -> Char -> FilePath
    mk t
    n Char
    c = t -> FilePath
    forall t. ConfigurableValue t => t -> FilePath
    val t
    n FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ [Char
    c]

    isKeepParam :: ObnamParam -> Bool
    isKeepParam :: FilePath -> Bool
    isKeepParam FilePath
    p = FilePath
    "--keep=" FilePath -> FilePath -> Bool
    forall a. Eq a => [a] -> [a] -> Bool
    `isPrefixOf` FilePath
    p

    installed :: Property DebianLike
    installed :: Property DebianLike
    installed = [FilePath] -> Property DebianLike
    Apt.installed [FilePath
    "obnam"]