From afab5b2f0b4e06a5c41f064d10f65ead063ab5af Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Mon, 27 Jul 2020 17:36:14 +0200 Subject: Borg.init: add the now required encryption type parameter The encryption type is now a required parameter when creating a repository. Unless you use no encryption, you must provide the repository passphrase, for example: withPrivData (Password "backups") (Context "borg") $ \getdata -> property' "borg repo" $ \w -> getdata $ \privdata -> ensureProperty w $ Borg.init (Borg.BorgRepoUsing [Borg.UsesEnvVar ("BORG_PASSPHRASE", privDataVal privdata)] "/path/to/backups") Borg.BorgEncKeyfile --- src/Propellor/Property/Borg.hs | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/Propellor/Property/Borg.hs b/src/Propellor/Property/Borg.hs index f662c8ee..075e53bc 100644 --- a/src/Propellor/Property/Borg.hs +++ b/src/Propellor/Property/Borg.hs @@ -6,6 +6,7 @@ module Propellor.Property.Borg ( BorgParam , BorgRepo(..) , BorgRepoOpt(..) + , BorgEnc(..) , installed , repoExists , init @@ -40,6 +41,27 @@ data BorgRepoOpt -- borg on a BorgRepo. | UsesEnvVar (String, String) +-- | Borg Encryption type. +data BorgEnc + -- | No encryption, no authentication. + = BorgEncNone + -- | Authenticated, using SHA-256 for hash/MAC. + | BorgEncAuthenticated + -- | Authenticated, using Blake2b for hash/MAC. + | BorgEncAuthenticatedBlake2 + -- | Encrypted, storing the key in the repository, using SHA-256 for + -- hash/MAC. + | BorgEncRepokey + -- | Encrypted, storing the key in the repository, using Blake2b for + -- hash/MAC. + | BorgEncRepokeyBlake2 + -- | Encrypted, storing the key outside of the repository, using + -- SHA-256 for hash/MAC. + | BorgEncKeyfile + -- | Encrypted, storing the key outside of the repository, using + -- Blake2b for hash/MAC. + | BorgEncKeyfileBlake2 + repoLoc :: BorgRepo -> String repoLoc (BorgRepo s) = s repoLoc (BorgRepoUsing _ s) = s @@ -74,13 +96,14 @@ repoExists :: BorgRepo -> IO Bool repoExists repo = runBorg repo [Param "list", Param (repoLoc repo)] -- | Inits a new borg repository -init :: BorgRepo -> Property DebianLike -init repo = check (not <$> repoExists repo) +init :: BorgRepo -> BorgEnc -> Property DebianLike +init repo enc = check (not <$> repoExists repo) (cmdPropertyEnv "borg" initargs (runBorgEnv repo)) `requires` installed where initargs = [ "init" + , encParam enc , repoLoc repo ] @@ -202,3 +225,13 @@ data KeepPolicy | KeepWeeks Int | KeepMonths Int | KeepYears Int + +-- | Construct the encryption type parameter. +encParam :: BorgEnc -> BorgParam +encParam BorgEncNone = "--encryption=none" +encParam BorgEncAuthenticated = "--encryption=authenticated" +encParam BorgEncAuthenticatedBlake2 = "--encryption=authenticated-blake2" +encParam BorgEncRepokey = "--encryption=repokey" +encParam BorgEncRepokeyBlake2 = "--encryption=repokey-blake2" +encParam BorgEncKeyfile = "--encryption=keyfile" +encParam BorgEncKeyfileBlake2 = "--encryption=keyfile-blake2" -- cgit v1.2.3 From a83b5c6e6b007b36fa19230d3a9420826ade4bf3 Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Mon, 27 Jul 2020 17:43:24 +0200 Subject: Borg: fix restoration When using borg extract, the result is extracted in the current directory. Also an archive name must be provided, so use the latest archive. --- src/Propellor/Property/Borg.hs | 52 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/src/Propellor/Property/Borg.hs b/src/Propellor/Property/Borg.hs index 075e53bc..62017704 100644 --- a/src/Propellor/Property/Borg.hs +++ b/src/Propellor/Property/Borg.hs @@ -20,6 +20,7 @@ import Prelude hiding (init) import qualified Propellor.Property.Apt as Apt import qualified Propellor.Property.Cron as Cron import Data.List (intercalate) +import Utility.SafeCommand (boolSystem') -- | Parameter to pass to a borg command. type BorgParam = String @@ -66,12 +67,22 @@ repoLoc :: BorgRepo -> String repoLoc (BorgRepo s) = s repoLoc (BorgRepoUsing _ s) = s -runBorg :: BorgRepo -> [CommandParam] -> IO Bool -runBorg repo ps = case runBorgEnv repo of - [] -> boolSystem "borg" ps +runBorg :: BorgRepo -> [CommandParam] -> Maybe FilePath -> IO Bool +runBorg repo ps chdir = case runBorgEnv repo of + [] -> runBorg' Nothing environ -> do environ' <- addEntries environ <$> getEnvironment - boolSystemEnv "borg" ps (Just environ') + runBorg' (Just environ') + where + runBorg' environ = boolSystem' "borg" ps $ + \p -> p { cwd = chdir, env = environ } + +readBorg :: BorgRepo -> [String] -> IO String +readBorg repo ps = case runBorgEnv repo of + [] -> readProcess "borg" ps + environ -> do + environ' <- addEntries environ <$> getEnvironment + readProcessEnv "borg" ps (Just environ') runBorgEnv :: BorgRepo -> [(String, String)] runBorgEnv (BorgRepo _) = [] @@ -93,7 +104,21 @@ installed = pickOS installdebian aptinstall desc = "installed borgbackup" repoExists :: BorgRepo -> IO Bool -repoExists repo = runBorg repo [Param "list", Param (repoLoc repo)] +repoExists repo = runBorg repo [Param "list", Param (repoLoc repo)] Nothing + +-- | Get the name of the latest archive, use a trick to ignore checkpoints +-- (idea from borgmatic, regular archives usually end with a timestamp). +latestArchive :: BorgRepo -> IO String +latestArchive repo = takeWhile (/= '\n') + <$> readBorg repo listargs + where + listargs = + [ "list" + , "--glob-archive=*[0123456789]" + , "--last=1" + , "--short" + , repoLoc repo + ] -- | Inits a new borg repository init :: BorgRepo -> BorgEnc -> Property DebianLike @@ -121,18 +146,25 @@ restored dir repo = go `requires` installed go = property (dir ++ " restored by borg") $ ifM (liftIO needsRestore) ( do warningMessage $ dir ++ " is empty/missing; restoring from backup ..." - liftIO restore + liftIO restoreLatest , noChange ) needsRestore = isUnpopulated dir - restore = withTmpDirIn (takeDirectory dir) "borg-restore" $ \tmpdir -> do - ok <- runBorg repo $ + restoreLatest = do + latest <- latestArchive repo + ifM (pure $ not $ null latest) + ( restore latest + , return FailedChange + ) + + restore latest = withTmpDirIn (takeDirectory dir) "borg-restore" $ \tmpdir -> do + ok <- runBorg repo [ Param "extract" - , Param (repoLoc repo) - , Param tmpdir + , Param ((repoLoc repo) ++ "::" ++ latest) ] + (Just tmpdir) let restoreddir = tmpdir ++ "/" ++ dir ifM (pure ok <&&> doesDirectoryExist restoreddir) ( do -- cgit v1.2.3 From 3555bf9d270336bc8f9075446da1c114af1c0478 Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Mon, 10 Aug 2020 22:54:19 +0200 Subject: Borg: handle old borg versions and issue a warning if no archive is found The --last and --glob-archive options need borg version 1.1. Stop using these options and do the filtering in haskell. This had the side benefit of having better checkpoint filtering. Also issue a warning in case no archive is found. --- src/Propellor/Property/Borg.hs | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/Propellor/Property/Borg.hs b/src/Propellor/Property/Borg.hs index 62017704..9fc0eacc 100644 --- a/src/Propellor/Property/Borg.hs +++ b/src/Propellor/Property/Borg.hs @@ -15,11 +15,11 @@ module Propellor.Property.Borg , KeepPolicy (..) ) where -import Propellor.Base hiding (init) +import Propellor.Base hiding (init, last) import Prelude hiding (init) import qualified Propellor.Property.Apt as Apt import qualified Propellor.Property.Cron as Cron -import Data.List (intercalate) +import Data.List (intercalate, isSuffixOf) import Utility.SafeCommand (boolSystem') -- | Parameter to pass to a borg command. @@ -106,16 +106,15 @@ installed = pickOS installdebian aptinstall repoExists :: BorgRepo -> IO Bool repoExists repo = runBorg repo [Param "list", Param (repoLoc repo)] Nothing --- | Get the name of the latest archive, use a trick to ignore checkpoints --- (idea from borgmatic, regular archives usually end with a timestamp). -latestArchive :: BorgRepo -> IO String -latestArchive repo = takeWhile (/= '\n') - <$> readBorg repo listargs +-- | Get the name of the latest archive. +latestArchive :: BorgRepo -> IO (Maybe String) +latestArchive repo = getLatest <$> readBorg repo listargs where + getLatest = maybeLast . filter (not . isSuffixOf ".checkpoint") . lines + maybeLast [] = Nothing + maybeLast ps = Just $ last ps listargs = [ "list" - , "--glob-archive=*[0123456789]" - , "--last=1" , "--short" , repoLoc repo ] @@ -146,19 +145,18 @@ restored dir repo = go `requires` installed go = property (dir ++ " restored by borg") $ ifM (liftIO needsRestore) ( do warningMessage $ dir ++ " is empty/missing; restoring from backup ..." - liftIO restoreLatest + latest <- liftIO (latestArchive repo) + case latest of + Nothing -> do + warningMessage $ "no archive to extract" + return FailedChange + Just l -> liftIO (restore l) , noChange ) needsRestore = isUnpopulated dir - restoreLatest = do - latest <- latestArchive repo - ifM (pure $ not $ null latest) - ( restore latest - , return FailedChange - ) - + restore :: String -> IO Result restore latest = withTmpDirIn (takeDirectory dir) "borg-restore" $ \tmpdir -> do ok <- runBorg repo [ Param "extract" -- cgit v1.2.3