2011-04-08 20:44:43 +00:00
|
|
|
|
{- Using bup as a remote.
|
|
|
|
|
-
|
|
|
|
|
- Copyright 2011 Joey Hess <joey@kitenet.net>
|
|
|
|
|
-
|
|
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
module Remote.Bup (remote) where
|
|
|
|
|
|
2011-04-17 03:01:29 +00:00
|
|
|
|
import qualified Data.ByteString.Lazy.Char8 as L
|
2011-07-15 16:47:14 +00:00
|
|
|
|
import System.IO.Error
|
2011-04-08 20:44:43 +00:00
|
|
|
|
import qualified Data.Map as M
|
|
|
|
|
import System.Process
|
|
|
|
|
|
2011-10-05 20:02:51 +00:00
|
|
|
|
import Common.Annex
|
2011-06-02 01:56:04 +00:00
|
|
|
|
import Types.Remote
|
2011-06-30 17:16:57 +00:00
|
|
|
|
import qualified Git
|
2011-12-14 19:56:11 +00:00
|
|
|
|
import qualified Git.Command
|
2011-12-13 19:05:07 +00:00
|
|
|
|
import qualified Git.Config
|
|
|
|
|
import qualified Git.Construct
|
2011-04-08 20:44:43 +00:00
|
|
|
|
import Config
|
2011-10-16 04:04:26 +00:00
|
|
|
|
import Annex.Ssh
|
2011-08-17 00:49:54 +00:00
|
|
|
|
import Remote.Helper.Special
|
|
|
|
|
import Remote.Helper.Encryptable
|
2011-04-17 03:01:29 +00:00
|
|
|
|
import Crypto
|
2011-04-08 20:44:43 +00:00
|
|
|
|
|
2011-04-09 19:36:54 +00:00
|
|
|
|
type BupRepo = String
|
|
|
|
|
|
2011-04-08 20:44:43 +00:00
|
|
|
|
remote :: RemoteType Annex
|
|
|
|
|
remote = RemoteType {
|
|
|
|
|
typename = "bup",
|
2011-04-09 16:41:17 +00:00
|
|
|
|
enumerate = findSpecialRemotes "buprepo",
|
2011-04-08 20:44:43 +00:00
|
|
|
|
generate = gen,
|
|
|
|
|
setup = bupSetup
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-15 19:09:36 +00:00
|
|
|
|
gen :: Git.Repo -> UUID -> Maybe RemoteConfig -> Annex (Remote Annex)
|
2011-04-08 20:44:43 +00:00
|
|
|
|
gen r u c = do
|
2011-04-09 16:41:17 +00:00
|
|
|
|
buprepo <- getConfig r "buprepo" (error "missing buprepo")
|
|
|
|
|
cst <- remoteCost r (if bupLocal buprepo then semiCheapRemoteCost else expensiveRemoteCost)
|
2011-04-09 19:36:54 +00:00
|
|
|
|
bupr <- liftIO $ bup2GitRemote buprepo
|
|
|
|
|
(u', bupr') <- getBupUUID bupr u
|
2011-04-09 01:37:59 +00:00
|
|
|
|
|
2011-04-17 04:40:23 +00:00
|
|
|
|
return $ encryptableRemote c
|
2011-04-17 03:01:29 +00:00
|
|
|
|
(storeEncrypted r buprepo)
|
|
|
|
|
(retrieveEncrypted buprepo)
|
|
|
|
|
Remote {
|
2011-04-09 16:41:17 +00:00
|
|
|
|
uuid = u',
|
2011-04-08 20:44:43 +00:00
|
|
|
|
cost = cst,
|
|
|
|
|
name = Git.repoDescribe r,
|
2011-04-09 16:41:17 +00:00
|
|
|
|
storeKey = store r buprepo,
|
|
|
|
|
retrieveKeyFile = retrieve buprepo,
|
2011-04-08 20:44:43 +00:00
|
|
|
|
removeKey = remove,
|
2011-04-17 03:01:29 +00:00
|
|
|
|
hasKey = checkPresent r bupr',
|
2011-04-28 18:39:51 +00:00
|
|
|
|
hasKeyCheap = bupLocal buprepo,
|
2011-09-19 00:11:39 +00:00
|
|
|
|
config = c,
|
|
|
|
|
repo = r
|
2011-04-08 20:44:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-04-15 19:09:36 +00:00
|
|
|
|
bupSetup :: UUID -> RemoteConfig -> Annex RemoteConfig
|
2011-04-08 20:44:43 +00:00
|
|
|
|
bupSetup u c = do
|
|
|
|
|
-- verify configuration is sane
|
2011-07-15 16:47:14 +00:00
|
|
|
|
let buprepo = fromMaybe (error "Specify buprepo=") $
|
2011-05-15 06:49:43 +00:00
|
|
|
|
M.lookup "buprepo" c
|
2011-04-16 17:25:27 +00:00
|
|
|
|
c' <- encryptionSetup c
|
2011-04-08 20:44:43 +00:00
|
|
|
|
|
|
|
|
|
-- bup init will create the repository.
|
|
|
|
|
-- (If the repository already exists, bup init again appears safe.)
|
2011-07-19 18:07:23 +00:00
|
|
|
|
showAction "bup init"
|
2011-05-17 15:44:13 +00:00
|
|
|
|
bup "init" buprepo [] >>! error "bup init failed"
|
2011-04-08 20:44:43 +00:00
|
|
|
|
|
2011-04-09 16:41:17 +00:00
|
|
|
|
storeBupUUID u buprepo
|
2011-04-09 16:34:49 +00:00
|
|
|
|
|
2011-04-09 16:41:17 +00:00
|
|
|
|
-- The buprepo is stored in git config, as well as this repo's
|
2011-04-08 20:44:43 +00:00
|
|
|
|
-- persistant state, so it can vary between hosts.
|
2011-04-16 17:25:27 +00:00
|
|
|
|
gitConfigSpecialRemote u c' "buprepo" buprepo
|
2011-04-08 20:44:43 +00:00
|
|
|
|
|
2011-04-16 17:25:27 +00:00
|
|
|
|
return c'
|
2011-04-08 20:44:43 +00:00
|
|
|
|
|
2011-04-09 19:36:54 +00:00
|
|
|
|
bupParams :: String -> BupRepo -> [CommandParam] -> [CommandParam]
|
2011-04-09 16:41:17 +00:00
|
|
|
|
bupParams command buprepo params =
|
2011-07-15 16:47:14 +00:00
|
|
|
|
Param command : [Param "-r", Param buprepo] ++ params
|
2011-04-08 20:44:43 +00:00
|
|
|
|
|
2011-04-09 19:36:54 +00:00
|
|
|
|
bup :: String -> BupRepo -> [CommandParam] -> Annex Bool
|
2011-04-09 16:41:17 +00:00
|
|
|
|
bup command buprepo params = do
|
2011-07-19 18:07:23 +00:00
|
|
|
|
showOutput -- make way for bup output
|
2011-04-09 16:41:17 +00:00
|
|
|
|
liftIO $ boolSystem "bup" $ bupParams command buprepo params
|
2011-04-08 20:44:43 +00:00
|
|
|
|
|
2011-04-17 04:34:38 +00:00
|
|
|
|
pipeBup :: [CommandParam] -> Maybe Handle -> Maybe Handle -> IO Bool
|
|
|
|
|
pipeBup params inh outh = do
|
|
|
|
|
p <- runProcess "bup" (toCommand params)
|
|
|
|
|
Nothing Nothing inh outh Nothing
|
|
|
|
|
ok <- waitForProcess p
|
|
|
|
|
case ok of
|
|
|
|
|
ExitSuccess -> return True
|
|
|
|
|
_ -> return False
|
|
|
|
|
|
2011-04-17 03:01:29 +00:00
|
|
|
|
bupSplitParams :: Git.Repo -> BupRepo -> Key -> CommandParam -> Annex [CommandParam]
|
|
|
|
|
bupSplitParams r buprepo k src = do
|
|
|
|
|
o <- getConfig r "bup-split-options" ""
|
|
|
|
|
let os = map Param $ words o
|
2011-07-19 18:07:23 +00:00
|
|
|
|
showOutput -- make way for bup output
|
2011-04-17 03:01:29 +00:00
|
|
|
|
return $ bupParams "split" buprepo
|
|
|
|
|
(os ++ [Param "-n", Param (show k), src])
|
|
|
|
|
|
2011-04-09 19:36:54 +00:00
|
|
|
|
store :: Git.Repo -> BupRepo -> Key -> Annex Bool
|
2011-04-09 16:41:17 +00:00
|
|
|
|
store r buprepo k = do
|
2011-11-29 02:43:51 +00:00
|
|
|
|
src <- inRepo $ gitAnnexLocation k
|
2011-04-17 03:01:29 +00:00
|
|
|
|
params <- bupSplitParams r buprepo k (File src)
|
|
|
|
|
liftIO $ boolSystem "bup" params
|
|
|
|
|
|
|
|
|
|
storeEncrypted :: Git.Repo -> BupRepo -> (Cipher, Key) -> Key -> Annex Bool
|
|
|
|
|
storeEncrypted r buprepo (cipher, enck) k = do
|
2011-11-29 02:43:51 +00:00
|
|
|
|
src <- inRepo $ gitAnnexLocation k
|
2011-04-17 03:01:29 +00:00
|
|
|
|
params <- bupSplitParams r buprepo enck (Param "-")
|
2011-11-11 00:24:24 +00:00
|
|
|
|
liftIO $ catchBoolIO $
|
2011-07-15 16:47:14 +00:00
|
|
|
|
withEncryptedHandle cipher (L.readFile src) $ \h ->
|
2011-04-17 04:34:38 +00:00
|
|
|
|
pipeBup params (Just h) Nothing
|
2011-04-08 20:44:43 +00:00
|
|
|
|
|
2011-04-09 19:36:54 +00:00
|
|
|
|
retrieve :: BupRepo -> Key -> FilePath -> Annex Bool
|
2011-04-09 16:41:17 +00:00
|
|
|
|
retrieve buprepo k f = do
|
|
|
|
|
let params = bupParams "join" buprepo [Param $ show k]
|
2011-11-11 00:24:24 +00:00
|
|
|
|
liftIO $ catchBoolIO $ do
|
2011-04-08 20:44:43 +00:00
|
|
|
|
tofile <- openFile f WriteMode
|
2011-04-17 04:34:38 +00:00
|
|
|
|
pipeBup params Nothing (Just tofile)
|
2011-04-08 20:44:43 +00:00
|
|
|
|
|
2011-04-17 03:01:29 +00:00
|
|
|
|
retrieveEncrypted :: BupRepo -> (Cipher, Key) -> FilePath -> Annex Bool
|
2011-04-17 04:57:11 +00:00
|
|
|
|
retrieveEncrypted buprepo (cipher, enck) f = do
|
|
|
|
|
let params = bupParams "join" buprepo [Param $ show enck]
|
2011-11-11 00:24:24 +00:00
|
|
|
|
liftIO $ catchBoolIO $ do
|
2011-04-17 04:57:11 +00:00
|
|
|
|
(pid, h) <- hPipeFrom "bup" $ toCommand params
|
2011-04-19 19:26:50 +00:00
|
|
|
|
withDecryptedContent cipher (L.hGetContents h) $ L.writeFile f
|
2011-04-17 04:57:11 +00:00
|
|
|
|
forceSuccess pid
|
|
|
|
|
return True
|
2011-04-17 03:01:29 +00:00
|
|
|
|
|
2011-04-08 20:44:43 +00:00
|
|
|
|
remove :: Key -> Annex Bool
|
|
|
|
|
remove _ = do
|
|
|
|
|
warning "content cannot be removed from bup remote"
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
{- Bup does not provide a way to tell if a given dataset is present
|
|
|
|
|
- in a bup repository. One way it to check if the git repository has
|
|
|
|
|
- a branch matching the name (as created by bup split -n).
|
|
|
|
|
-}
|
2011-11-09 22:33:15 +00:00
|
|
|
|
checkPresent :: Git.Repo -> Git.Repo -> Key -> Annex (Either String Bool)
|
2011-04-09 19:36:54 +00:00
|
|
|
|
checkPresent r bupr k
|
|
|
|
|
| Git.repoIsUrl bupr = do
|
2011-07-19 18:07:23 +00:00
|
|
|
|
showAction $ "checking " ++ Git.repoDescribe r
|
2011-04-09 19:36:54 +00:00
|
|
|
|
ok <- onBupRemote bupr boolSystem "git" params
|
|
|
|
|
return $ Right ok
|
2011-11-11 00:24:24 +00:00
|
|
|
|
| otherwise = liftIO $ catchMsgIO $
|
2011-12-14 19:56:11 +00:00
|
|
|
|
boolSystem "git" $ Git.Command.gitCommandLine params bupr
|
2011-04-09 19:36:54 +00:00
|
|
|
|
where
|
|
|
|
|
params =
|
|
|
|
|
[ Params "show-ref --quiet --verify"
|
|
|
|
|
, Param $ "refs/heads/" ++ show k]
|
2011-04-09 16:34:49 +00:00
|
|
|
|
|
|
|
|
|
{- Store UUID in the annex.uuid setting of the bup repository. -}
|
2011-04-09 19:36:54 +00:00
|
|
|
|
storeBupUUID :: UUID -> BupRepo -> Annex ()
|
2011-04-09 16:41:17 +00:00
|
|
|
|
storeBupUUID u buprepo = do
|
|
|
|
|
r <- liftIO $ bup2GitRemote buprepo
|
2011-04-09 16:34:49 +00:00
|
|
|
|
if Git.repoIsUrl r
|
|
|
|
|
then do
|
2011-07-19 18:07:23 +00:00
|
|
|
|
showAction "storing uuid"
|
2011-05-17 07:10:13 +00:00
|
|
|
|
onBupRemote r boolSystem "git"
|
2011-11-08 03:21:22 +00:00
|
|
|
|
[Params $ "config annex.uuid " ++ v]
|
2011-05-17 15:44:13 +00:00
|
|
|
|
>>! error "ssh failed"
|
2011-04-09 16:34:49 +00:00
|
|
|
|
else liftIO $ do
|
2011-12-13 19:05:07 +00:00
|
|
|
|
r' <- Git.Config.read r
|
|
|
|
|
let olduuid = Git.Config.get "annex.uuid" "" r'
|
2011-11-08 19:34:10 +00:00
|
|
|
|
when (olduuid == "") $
|
2011-12-14 19:56:11 +00:00
|
|
|
|
Git.Command.run "config"
|
2011-11-08 19:34:10 +00:00
|
|
|
|
[Param "annex.uuid", Param v] r'
|
2011-11-08 03:21:22 +00:00
|
|
|
|
where
|
|
|
|
|
v = fromUUID u
|
2011-04-09 16:34:49 +00:00
|
|
|
|
|
2011-04-09 19:36:54 +00:00
|
|
|
|
onBupRemote :: Git.Repo -> (FilePath -> [CommandParam] -> IO a) -> FilePath -> [CommandParam] -> Annex a
|
|
|
|
|
onBupRemote r a command params = do
|
|
|
|
|
let dir = shellEscape (Git.workTree r)
|
|
|
|
|
sshparams <- sshToRepo r [Param $
|
2011-07-15 16:47:14 +00:00
|
|
|
|
"cd " ++ dir ++ " && " ++ unwords (command : toCommand params)]
|
2011-04-09 19:36:54 +00:00
|
|
|
|
liftIO $ a "ssh" sshparams
|
|
|
|
|
|
2011-04-09 16:41:17 +00:00
|
|
|
|
{- Allow for bup repositories on removable media by checking
|
2011-04-09 16:59:18 +00:00
|
|
|
|
- local bup repositories to see if they are available, and getting their
|
|
|
|
|
- uuid (which may be different from the stored uuid for the bup remote).
|
|
|
|
|
-
|
2011-12-10 22:51:01 +00:00
|
|
|
|
- If a bup repository is not available, returns NoUUID.
|
2011-04-09 16:59:18 +00:00
|
|
|
|
- This will cause checkPresent to indicate nothing from the bup remote
|
|
|
|
|
- is known to be present.
|
2011-04-09 19:36:54 +00:00
|
|
|
|
-
|
|
|
|
|
- Also, returns a version of the repo with config read, if it is local.
|
2011-04-09 16:59:18 +00:00
|
|
|
|
-}
|
2011-04-09 19:36:54 +00:00
|
|
|
|
getBupUUID :: Git.Repo -> UUID -> Annex (UUID, Git.Repo)
|
|
|
|
|
getBupUUID r u
|
|
|
|
|
| Git.repoIsUrl r = return (u, r)
|
|
|
|
|
| otherwise = liftIO $ do
|
2011-12-13 19:05:07 +00:00
|
|
|
|
ret <- try $ Git.Config.read r
|
2011-04-09 19:36:54 +00:00
|
|
|
|
case ret of
|
2011-12-13 19:05:07 +00:00
|
|
|
|
Right r' -> return (toUUID $ Git.Config.get "annex.uuid" "" r', r')
|
2011-11-07 18:46:01 +00:00
|
|
|
|
Left _ -> return (NoUUID, r)
|
2011-04-09 16:41:17 +00:00
|
|
|
|
|
2011-04-09 16:34:49 +00:00
|
|
|
|
{- Converts a bup remote path spec into a Git.Repo. There are some
|
|
|
|
|
- differences in path representation between git and bup. -}
|
2011-04-09 19:36:54 +00:00
|
|
|
|
bup2GitRemote :: BupRepo -> IO Git.Repo
|
2011-04-09 16:34:49 +00:00
|
|
|
|
bup2GitRemote "" = do
|
|
|
|
|
-- bup -r "" operates on ~/.bup
|
|
|
|
|
h <- myHomeDir
|
2011-12-13 19:05:07 +00:00
|
|
|
|
Git.Construct.fromAbsPath $ h </> ".bup"
|
2011-04-09 16:34:49 +00:00
|
|
|
|
bup2GitRemote r
|
|
|
|
|
| bupLocal r =
|
2011-07-15 16:47:14 +00:00
|
|
|
|
if head r == '/'
|
2011-12-13 19:05:07 +00:00
|
|
|
|
then Git.Construct.fromAbsPath r
|
2011-04-09 16:34:49 +00:00
|
|
|
|
else error "please specify an absolute path"
|
2011-12-13 19:05:07 +00:00
|
|
|
|
| otherwise = Git.Construct.fromUrl $ "ssh://" ++ host ++ slash dir
|
2011-04-09 16:34:49 +00:00
|
|
|
|
where
|
|
|
|
|
bits = split ":" r
|
2011-07-15 16:47:14 +00:00
|
|
|
|
host = head bits
|
2011-04-09 16:34:49 +00:00
|
|
|
|
dir = join ":" $ drop 1 bits
|
|
|
|
|
-- "host:~user/dir" is not supported specially by bup;
|
|
|
|
|
-- "host:dir" is relative to the home directory;
|
|
|
|
|
-- "host:" goes in ~/.bup
|
|
|
|
|
slash d
|
|
|
|
|
| d == "" = "/~/.bup"
|
2011-07-15 16:47:14 +00:00
|
|
|
|
| head d == '/' = d
|
2011-04-09 16:34:49 +00:00
|
|
|
|
| otherwise = "/~/" ++ d
|
|
|
|
|
|
2011-04-09 19:36:54 +00:00
|
|
|
|
bupLocal :: BupRepo -> Bool
|
2011-04-09 16:34:49 +00:00
|
|
|
|
bupLocal = notElem ':'
|