    -- | Maintainer: Félix Sipma <felix+propellor@gueux.org>
    --
    -- Support for the Attic backup tool <https://attic-backup.org/>
    --
    -- This module is deprecated because Attic is not available in debian
    -- stable any longer (so the installed property no longer works), and it
    -- appears to have been mostly supersceded by Borg.

    module Propellor.Property.Attic {-# DEPRECATED "Use Borg instead" #-}
    	( installed
    	, repoExists
    	, init
    	, restored
    	, backup
    	, KeepPolicy (..)
    	) where

    import Propellor.Base hiding (init)
    import Prelude hiding (init)
    import qualified Propellor.Property.Apt as Apt
    import qualified Propellor.Property.Cron as Cron
    import Data.List (intercalate)

    type AtticParam = String

    type AtticRepo = FilePath

    installed :: Property DebianLike
    installed :: Property DebianLike
    installed = [Package] -> Property DebianLike
    Apt.installed ["attic"]

    repoExists :: AtticRepo -> IO Bool
    repoExists :: Package -> IO Bool
    repoExists repo :: Package
    repo = Package -> [CommandParam] -> IO Bool
    boolSystem "attic" [Package -> CommandParam
    Param "list", Package -> CommandParam
    File Package
    repo]

    -- | Inits a new attic repository
    init :: AtticRepo -> Property DebianLike
    init :: Package -> Property DebianLike
    init backupdir :: Package
    backupdir = IO Bool -> UncheckedProperty UnixLike -> Property UnixLike
    forall (p :: * -> *) i (m :: * -> *).
    (Checkable p i, LiftPropellor m) =>
    m Bool -> p i -> Property i
    check (Bool -> Bool
    not (Bool -> Bool) -> IO Bool -> IO Bool
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Package -> IO Bool
    repoExists Package
    backupdir) (Package -> [Package] -> UncheckedProperty UnixLike
    cmdProperty "attic" [Package]
    initargs)
    	Property UnixLike
    -> Property DebianLike
    -> CombinedType (Property UnixLike) (Property DebianLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires` Property DebianLike
    installed
      where
    	initargs :: [Package]
    initargs =
    		[ "init"
    		, Package
    backupdir
    		]

    -- | Restores a directory from an attic 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 -> AtticRepo -> Property DebianLike
    restored :: Package -> Package -> Property DebianLike
    restored dir :: Package
    dir backupdir :: Package
    backupdir = 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
    	go :: Property DebianLike
    	go :: Property DebianLike
    go = Package -> Propellor Result -> Property DebianLike
    forall k (metatypes :: k).
    SingI metatypes =>
    Package -> Propellor Result -> Property (MetaTypes metatypes)
    property (Package
    dir Package -> Package -> Package
    forall a. [a] -> [a] -> [a]
    ++ " restored by attic") (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 (m :: * -> *) a. MonadIO m => IO a -> m a
    liftIO IO Bool
    needsRestore)
    		( do
    			Package -> Propellor ()
    forall (m :: * -> *). MonadIO m => Package -> m ()
    warningMessage (Package -> Propellor ()) -> Package -> Propellor ()
    forall a b. (a -> b) -> a -> b
    $ Package
    dir Package -> Package -> Package
    forall a. [a] -> [a] -> [a]
    ++ " is empty/missing; restoring from backup ..."
    			IO Result -> Propellor Result
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
    liftIO IO Result
    restore
    		, Propellor Result
    noChange
    		)

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

    	restore :: IO Result
    restore = Package -> Package -> (Package -> IO Result) -> IO Result
    forall (m :: * -> *) a.
    (MonadMask m, MonadIO m) =>
    Package -> Package -> (Package -> m a) -> m a
    withTmpDirIn (Package -> Package
    takeDirectory Package
    dir) "attic-restore" ((Package -> IO Result) -> IO Result)
    -> (Package -> IO Result) -> IO Result
    forall a b. (a -> b) -> a -> b
    $ \tmpdir :: Package
    tmpdir -> do
    		Bool
    ok <- Package -> [CommandParam] -> IO Bool
    boolSystem "attic" ([CommandParam] -> IO Bool) -> [CommandParam] -> IO Bool
    forall a b. (a -> b) -> a -> b
    $
    			[ Package -> CommandParam
    Param "extract"
    			, Package -> CommandParam
    Param Package
    backupdir
    			, Package -> CommandParam
    Param Package
    tmpdir
    			]
    		let restoreddir :: Package
    restoreddir = Package
    tmpdir Package -> Package -> Package
    forall a. [a] -> [a] -> [a]
    ++ "/" Package -> Package -> Package
    forall a. [a] -> [a] -> [a]
    ++ Package
    dir
    		IO Bool -> (IO Result, IO Result) -> IO Result
    forall (m :: * -> *) a. Monad m => m Bool -> (m a, m a) -> m a
    ifM (Bool -> IO Bool
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure Bool
    ok IO Bool -> IO Bool -> IO Bool
    forall (m :: * -> *). Monad m => m Bool -> m Bool -> m Bool
    <&&> Package -> IO Bool
    doesDirectoryExist Package
    restoreddir)
    			( do
    				IO (Either IOException ()) -> IO ()
    forall (f :: * -> *) a. Functor f => f a -> f ()
    void (IO (Either IOException ()) -> IO ())
    -> IO (Either IOException ()) -> IO ()
    forall a b. (a -> b) -> a -> b
    $ IO () -> IO (Either IOException ())
    forall (m :: * -> *) a.
    MonadCatch m =>
    m a -> m (Either IOException a)
    tryIO (IO () -> IO (Either IOException ()))
    -> IO () -> IO (Either IOException ())
    forall a b. (a -> b) -> a -> b
    $ Package -> IO ()
    removeDirectory Package
    dir
    				Package -> Package -> IO ()
    renameDirectory Package
    restoreddir Package
    dir
    				Result -> IO Result
    forall (m :: * -> *) a. Monad m => a -> m a
    return Result
    MadeChange
    			, Result -> IO Result
    forall (m :: * -> *) a. Monad m => a -> m a
    return Result
    FailedChange
    			)

    -- | Installs a cron job that causes a given directory to be backed
    -- up, by running attic 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:
    --
    -- >	& Attic.backup "/srv/git" "root@myserver:/mnt/backup/git.attic" Cron.Daily
    -- >		["--exclude=/srv/git/tobeignored"]
    -- >		[Attic.KeepDays 7, Attic.KeepWeeks 4, Attic.KeepMonths 6, Attic.KeepYears 1]
    --
    -- Note that this property does not make attic encrypt the backup
    -- repository.
    --
    -- Since attic uses a fair amount of system resources, only one attic
    -- backup job will be run at a time. Other jobs will wait their turns to
    -- run.
    backup :: FilePath -> AtticRepo -> Cron.Times -> [AtticParam] -> [KeepPolicy] -> Property DebianLike
    backup :: Package
    -> Package
    -> Times
    -> [Package]
    -> [KeepPolicy]
    -> Property DebianLike
    backup dir :: Package
    dir backupdir :: Package
    backupdir crontimes :: Times
    crontimes extraargs :: [Package]
    extraargs kp :: [KeepPolicy]
    kp = Package
    -> Package
    -> Times
    -> [Package]
    -> [KeepPolicy]
    -> Property DebianLike
    backup' Package
    dir Package
    backupdir Times
    crontimes [Package]
    extraargs [KeepPolicy]
    kp
    	Property DebianLike
    -> Property DebianLike
    -> CombinedType (Property DebianLike) (Property DebianLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires` Package -> Package -> Property DebianLike
    restored Package
    dir Package
    backupdir

    -- | Does a backup, but does not automatically restore.
    backup' :: FilePath -> AtticRepo -> Cron.Times -> [AtticParam] -> [KeepPolicy] -> Property DebianLike
    backup' :: Package
    -> Package
    -> Times
    -> [Package]
    -> [KeepPolicy]
    -> Property DebianLike
    backup' dir :: Package
    dir backupdir :: Package
    backupdir crontimes :: Times
    crontimes extraargs :: [Package]
    extraargs kp :: [KeepPolicy]
    kp = Property DebianLike
    cronjob
    	Property DebianLike -> Package -> Property DebianLike
    forall p. IsProp p => p -> Package -> p
    `describe` Package
    desc
    	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 :: Package
    desc = Package
    backupdir Package -> Package -> Package
    forall a. [a] -> [a] -> [a]
    ++ " attic backup"
    	cronjob :: Property DebianLike
    cronjob = Package
    -> Times -> User -> Package -> Package -> Property DebianLike
    Cron.niceJob ("attic_backup" Package -> Package -> Package
    forall a. [a] -> [a] -> [a]
    ++ Package
    dir) Times
    crontimes (Package -> User
    User "root") "/" (Package -> Property DebianLike) -> Package -> Property DebianLike
    forall a b. (a -> b) -> a -> b
    $
    		"flock " Package -> Package -> Package
    forall a. [a] -> [a] -> [a]
    ++ Package -> Package
    shellEscape Package
    lockfile Package -> Package -> Package
    forall a. [a] -> [a] -> [a]
    ++ " sh -c " Package -> Package -> Package
    forall a. [a] -> [a] -> [a]
    ++ Package -> Package
    shellEscape Package
    backupcmd
    	lockfile :: Package
    lockfile = "/var/lock/propellor-attic.lock"
    	backupcmd :: Package
    backupcmd = Package -> [Package] -> Package
    forall a. [a] -> [[a]] -> [a]
    intercalate ";" ([Package] -> Package) -> [Package] -> Package
    forall a b. (a -> b) -> a -> b
    $
    		Package
    createCommand
    		Package -> [Package] -> [Package]
    forall a. a -> [a] -> [a]
    : if [KeepPolicy] -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null [KeepPolicy]
    kp then [] else [Package
    pruneCommand]
    	createCommand :: Package
    createCommand = [Package] -> Package
    unwords ([Package] -> Package) -> [Package] -> Package
    forall a b. (a -> b) -> a -> b
    $
    		[ "attic"
    		, "create"
    		, "--stats"
    		]
    		[Package] -> [Package] -> [Package]
    forall a. [a] -> [a] -> [a]
    ++ (Package -> Package) -> [Package] -> [Package]
    forall a b. (a -> b) -> [a] -> [b]
    map Package -> Package
    shellEscape [Package]
    extraargs [Package] -> [Package] -> [Package]
    forall a. [a] -> [a] -> [a]
    ++
    		[ Package -> Package
    shellEscape Package
    backupdir Package -> Package -> Package
    forall a. [a] -> [a] -> [a]
    ++ "::" Package -> Package -> Package
    forall a. [a] -> [a] -> [a]
    ++ "$(date --iso-8601=ns --utc)"
    		, Package -> Package
    shellEscape Package
    dir
    		]
    	pruneCommand :: Package
    pruneCommand = [Package] -> Package
    unwords ([Package] -> Package) -> [Package] -> Package
    forall a b. (a -> b) -> a -> b
    $
    		[ "attic"
    		, "prune"
    		, Package -> Package
    shellEscape Package
    backupdir
    		]
    		[Package] -> [Package] -> [Package]
    forall a. [a] -> [a] -> [a]
    ++
    		(KeepPolicy -> Package) -> [KeepPolicy] -> [Package]
    forall a b. (a -> b) -> [a] -> [b]
    map KeepPolicy -> Package
    keepParam [KeepPolicy]
    kp

    -- | Constructs an AtticParam that specifies which old backup generations to
    -- keep. By default, all generations are kept. However, when this parameter is
    -- passed to the `backup` property, they will run attic prune to clean out
    -- generations not specified here.
    keepParam :: KeepPolicy -> AtticParam
    keepParam :: KeepPolicy -> Package
    keepParam (KeepHours n :: Int
    n) = "--keep-hourly=" Package -> Package -> Package
    forall a. [a] -> [a] -> [a]
    ++ Int -> Package
    forall t. ConfigurableValue t => t -> Package
    val Int
    n
    keepParam (KeepDays n :: Int
    n) = "--keep-daily=" Package -> Package -> Package
    forall a. [a] -> [a] -> [a]
    ++ Int -> Package
    forall t. ConfigurableValue t => t -> Package
    val Int
    n
    keepParam (KeepWeeks n :: Int
    n) = "--keep-daily=" Package -> Package -> Package
    forall a. [a] -> [a] -> [a]
    ++ Int -> Package
    forall t. ConfigurableValue t => t -> Package
    val Int
    n
    keepParam (KeepMonths n :: Int
    n) = "--keep-monthly=" Package -> Package -> Package
    forall a. [a] -> [a] -> [a]
    ++ Int -> Package
    forall t. ConfigurableValue t => t -> Package
    val Int
    n
    keepParam (KeepYears n :: Int
    n) = "--keep-yearly=" Package -> Package -> Package
    forall a. [a] -> [a] -> [a]
    ++ Int -> Package
    forall t. ConfigurableValue t => t -> Package
    val Int
    n

    -- | 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 attic's man page for details.
    data KeepPolicy
    	= KeepHours Int
    	| KeepDays Int
    	| KeepWeeks Int
    	| KeepMonths Int
    	| KeepYears Int