2012-09-11 01:55:59 +00:00
|
|
|
{- git-annex assistant pairing remote creation
|
|
|
|
-
|
2015-01-21 16:50:09 +00:00
|
|
|
- Copyright 2012 Joey Hess <id@joeyh.name>
|
2012-09-11 01:55:59 +00:00
|
|
|
-
|
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
|
|
|
module Assistant.Pairing.MakeRemote where
|
|
|
|
|
|
|
|
import Assistant.Common
|
|
|
|
import Assistant.Ssh
|
|
|
|
import Assistant.Pairing
|
|
|
|
import Assistant.Pairing.Network
|
|
|
|
import Assistant.MakeRemote
|
2013-09-29 18:39:10 +00:00
|
|
|
import Assistant.Sync
|
2013-03-13 20:16:01 +00:00
|
|
|
import Config.Cost
|
2013-09-29 18:39:10 +00:00
|
|
|
import Config
|
external special remotes mostly implemented (untested)
This has not been tested at all. It compiles!
The only known missing things are support for encryption, and for get/set
of special remote configuration, and of key state. (The latter needs
separate work to add a new per-key log file to store that state.)
Only thing I don't much like is that initremote needs to be passed both
type=external and externaltype=foo. It would be better to have just
type=foo
Most of this is quite straightforward code, that largely wrote itself given
the types. The only tricky parts were:
* Need to lock the remote when using it to eg make a request, because
in theory git-annex could have multiple threads that each try to use
a remote at the same time. I don't think that git-annex ever does
that currently, but better safe than sorry.
* Rather than starting up every external special remote program when
git-annex starts, they are started only on demand, when first used.
This will avoid slowdown, especially when running fast git-annex query
commands. Once started, they keep running until git-annex stops, currently,
which may not be ideal, but it's hard to know a better time to stop them.
* Bit of a chicken and egg problem with caching the cost of the remote,
because setting annex-cost in the git config needs the remote to already
be set up. Managed to finesse that.
This commit was sponsored by Lukas Anzinger.
2013-12-26 22:23:13 +00:00
|
|
|
import qualified Types.Remote as Remote
|
2012-09-11 01:55:59 +00:00
|
|
|
|
|
|
|
import Network.Socket
|
|
|
|
import qualified Data.Text as T
|
|
|
|
|
2012-09-11 04:23:34 +00:00
|
|
|
{- Authorized keys are set up before pairing is complete, so that the other
|
|
|
|
- side can immediately begin syncing. -}
|
2012-11-05 16:21:13 +00:00
|
|
|
setupAuthorizedKeys :: PairMsg -> FilePath -> IO ()
|
2015-02-04 18:05:27 +00:00
|
|
|
setupAuthorizedKeys msg repodir = case validateSshPubKey $ remoteSshPubKey $ pairMsgData msg of
|
|
|
|
Left err -> error err
|
2015-07-02 19:05:12 +00:00
|
|
|
Right pubkey -> do
|
|
|
|
absdir <- absPath repodir
|
|
|
|
unlessM (liftIO $ addAuthorizedKeys True absdir pubkey) $
|
2015-02-04 18:05:27 +00:00
|
|
|
error "failed setting up ssh authorized keys"
|
2012-09-11 04:23:34 +00:00
|
|
|
|
2012-11-05 21:43:17 +00:00
|
|
|
{- When local pairing is complete, this is used to set up the remote for
|
|
|
|
- the host we paired with. -}
|
|
|
|
finishedLocalPairing :: PairMsg -> SshKeyPair -> Assistant ()
|
|
|
|
finishedLocalPairing msg keypair = do
|
2015-07-20 22:38:23 +00:00
|
|
|
sshdata <- liftIO $ installSshKeyPair keypair =<< pairMsgToSshData msg
|
2012-10-29 18:07:12 +00:00
|
|
|
{- Ensure that we know the ssh host key for the host we paired with.
|
2012-09-11 01:55:59 +00:00
|
|
|
- If we don't, ssh over to get it. -}
|
2012-10-29 18:07:12 +00:00
|
|
|
liftIO $ unlessM (knownHost $ sshHostName sshdata) $
|
2012-09-11 01:55:59 +00:00
|
|
|
void $ sshTranscript
|
|
|
|
[ sshOpt "StrictHostKeyChecking" "no"
|
|
|
|
, sshOpt "NumberOfPasswordPrompts" "0"
|
|
|
|
, "-n"
|
|
|
|
, genSshHost (sshHostName sshdata) (sshUserName sshdata)
|
|
|
|
, "git-annex-shell -c configlist " ++ T.unpack (sshDirectory sshdata)
|
|
|
|
]
|
2013-02-26 17:04:37 +00:00
|
|
|
Nothing
|
2013-10-01 17:43:35 +00:00
|
|
|
r <- liftAnnex $ addRemote $ makeSshRemote sshdata
|
external special remotes mostly implemented (untested)
This has not been tested at all. It compiles!
The only known missing things are support for encryption, and for get/set
of special remote configuration, and of key state. (The latter needs
separate work to add a new per-key log file to store that state.)
Only thing I don't much like is that initremote needs to be passed both
type=external and externaltype=foo. It would be better to have just
type=foo
Most of this is quite straightforward code, that largely wrote itself given
the types. The only tricky parts were:
* Need to lock the remote when using it to eg make a request, because
in theory git-annex could have multiple threads that each try to use
a remote at the same time. I don't think that git-annex ever does
that currently, but better safe than sorry.
* Rather than starting up every external special remote program when
git-annex starts, they are started only on demand, when first used.
This will avoid slowdown, especially when running fast git-annex query
commands. Once started, they keep running until git-annex stops, currently,
which may not be ideal, but it's hard to know a better time to stop them.
* Bit of a chicken and egg problem with caching the cost of the remote,
because setting annex-cost in the git config needs the remote to already
be set up. Managed to finesse that.
This commit was sponsored by Lukas Anzinger.
2013-12-26 22:23:13 +00:00
|
|
|
liftAnnex $ setRemoteCost (Remote.repo r) semiExpensiveRemoteCost
|
2013-09-29 18:39:10 +00:00
|
|
|
syncRemote r
|
2012-09-11 01:55:59 +00:00
|
|
|
|
|
|
|
{- Mostly a straightforward conversion. Except:
|
|
|
|
- * Determine the best hostname to use to contact the host.
|
|
|
|
- * Strip leading ~/ from the directory name.
|
|
|
|
-}
|
|
|
|
pairMsgToSshData :: PairMsg -> IO SshData
|
|
|
|
pairMsgToSshData msg = do
|
|
|
|
let d = pairMsgData msg
|
|
|
|
hostname <- liftIO $ bestHostName msg
|
|
|
|
let dir = case remoteDirectory d of
|
|
|
|
('~':'/':v) -> v
|
|
|
|
v -> v
|
2012-09-13 04:57:52 +00:00
|
|
|
return SshData
|
2012-09-11 01:55:59 +00:00
|
|
|
{ sshHostName = T.pack hostname
|
|
|
|
, sshUserName = Just (T.pack $ remoteUserName d)
|
|
|
|
, sshDirectory = T.pack dir
|
|
|
|
, sshRepoName = genSshRepoName hostname dir
|
2012-12-06 21:09:08 +00:00
|
|
|
, sshPort = 22
|
2012-09-11 01:55:59 +00:00
|
|
|
, needsPubKey = True
|
2013-09-29 18:39:10 +00:00
|
|
|
, sshCapabilities = [GitAnnexShellCapable, GitCapable, RsyncCapable]
|
2015-07-20 22:38:23 +00:00
|
|
|
, sshRepoUrl = Nothing
|
2012-09-13 04:57:52 +00:00
|
|
|
}
|
2012-09-11 01:55:59 +00:00
|
|
|
|
|
|
|
{- Finds the best hostname to use for the host that sent the PairMsg.
|
|
|
|
-
|
|
|
|
- If remoteHostName is set, tries to use a .local address based on it.
|
|
|
|
- That's the most robust, if this system supports .local.
|
|
|
|
- Otherwise, looks up the hostname in the DNS for the remoteAddress,
|
|
|
|
- if any. May fall back to remoteAddress if there's no DNS. Ugh. -}
|
|
|
|
bestHostName :: PairMsg -> IO HostName
|
2012-09-13 04:57:52 +00:00
|
|
|
bestHostName msg = case remoteHostName $ pairMsgData msg of
|
2012-09-11 01:55:59 +00:00
|
|
|
Just h -> do
|
|
|
|
let localname = h ++ ".local"
|
2012-09-17 04:18:07 +00:00
|
|
|
addrs <- catchDefaultIO [] $
|
|
|
|
getAddrInfo Nothing (Just localname) Nothing
|
2012-09-11 01:55:59 +00:00
|
|
|
maybe fallback (const $ return localname) (headMaybe addrs)
|
|
|
|
Nothing -> fallback
|
2012-10-31 06:34:03 +00:00
|
|
|
where
|
|
|
|
fallback = do
|
|
|
|
let a = pairMsgAddr msg
|
|
|
|
let sockaddr = case a of
|
2015-05-10 19:49:47 +00:00
|
|
|
IPv4Addr addr -> SockAddrInet (fromInteger 0) addr
|
|
|
|
IPv6Addr addr -> SockAddrInet6 (fromInteger 0) 0 addr 0
|
2012-10-31 06:34:03 +00:00
|
|
|
fromMaybe (showAddr a)
|
|
|
|
<$> catchDefaultIO Nothing
|
|
|
|
(fst <$> getNameInfo [] True False sockaddr)
|