pairing probably works now (untested)
This commit is contained in:
parent
a41255723c
commit
d19bbd29d8
11 changed files with 323 additions and 229 deletions
|
@ -181,7 +181,7 @@ startAssistant assistant daemonize webappwaiter = do
|
||||||
#ifdef WITH_WEBAPP
|
#ifdef WITH_WEBAPP
|
||||||
, assist $ webAppThread (Just st) dstatus scanremotes transferqueue transferslots urlrenderer Nothing webappwaiter
|
, assist $ webAppThread (Just st) dstatus scanremotes transferqueue transferslots urlrenderer Nothing webappwaiter
|
||||||
#ifdef WITH_PAIRING
|
#ifdef WITH_PAIRING
|
||||||
, assist $ pairListenerThread st dstatus urlrenderer
|
, assist $ pairListenerThread st dstatus scanremotes urlrenderer
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
, assist $ pushThread st dstatus commitchan pushmap
|
, assist $ pushThread st dstatus commitchan pushmap
|
||||||
|
|
107
Assistant/MakeRemote.hs
Normal file
107
Assistant/MakeRemote.hs
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
{- git-annex assistant remote creation utilities
|
||||||
|
-
|
||||||
|
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
||||||
|
-
|
||||||
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Assistant.MakeRemote where
|
||||||
|
|
||||||
|
import Assistant.Common
|
||||||
|
import Assistant.ThreadedMonad
|
||||||
|
import Assistant.DaemonStatus
|
||||||
|
import Assistant.ScanRemotes
|
||||||
|
import Assistant.Ssh
|
||||||
|
import Assistant.Sync
|
||||||
|
import qualified Types.Remote as R
|
||||||
|
import qualified Remote
|
||||||
|
import Remote.List
|
||||||
|
import qualified Remote.Rsync as Rsync
|
||||||
|
import qualified Git
|
||||||
|
import qualified Git.Command
|
||||||
|
import qualified Command.InitRemote
|
||||||
|
import Logs.UUID
|
||||||
|
import Logs.Remote
|
||||||
|
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Map as M
|
||||||
|
|
||||||
|
{- Sets up and begins syncing with a new ssh or rsync remote. -}
|
||||||
|
makeSshRemote :: ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> Bool -> SshData -> IO ()
|
||||||
|
makeSshRemote st dstatus scanremotes forcersync sshdata = do
|
||||||
|
r <- runThreadState st $
|
||||||
|
addRemote $ maker (sshRepoName sshdata) sshurl
|
||||||
|
syncNewRemote st dstatus scanremotes r
|
||||||
|
where
|
||||||
|
rsync = forcersync || rsyncOnly sshdata
|
||||||
|
maker
|
||||||
|
| rsync = makeRsyncRemote
|
||||||
|
| otherwise = makeGitRemote
|
||||||
|
sshurl = T.unpack $ T.concat $
|
||||||
|
if rsync
|
||||||
|
then [u, h, T.pack ":", sshDirectory sshdata, T.pack "/"]
|
||||||
|
else [T.pack "ssh://", u, h, d, T.pack "/"]
|
||||||
|
where
|
||||||
|
u = maybe (T.pack "") (\v -> T.concat [v, T.pack "@"]) $ sshUserName sshdata
|
||||||
|
h = sshHostName sshdata
|
||||||
|
d
|
||||||
|
| T.pack "/" `T.isPrefixOf` sshDirectory sshdata = d
|
||||||
|
| otherwise = T.concat [T.pack "/~/", sshDirectory sshdata]
|
||||||
|
|
||||||
|
{- Runs an action that returns a name of the remote, and finishes adding it. -}
|
||||||
|
addRemote :: Annex String -> Annex Remote
|
||||||
|
addRemote a = do
|
||||||
|
name <- a
|
||||||
|
void $ remoteListRefresh
|
||||||
|
maybe (error "failed to add remote") return =<< Remote.byName (Just name)
|
||||||
|
|
||||||
|
{- Inits a rsync special remote, and returns the name of the remote. -}
|
||||||
|
makeRsyncRemote :: String -> String -> Annex String
|
||||||
|
makeRsyncRemote name location = makeRemote name location $ const $ do
|
||||||
|
(u, c) <- Command.InitRemote.findByName name
|
||||||
|
c' <- R.setup Rsync.remote u $ M.union config c
|
||||||
|
describeUUID u name
|
||||||
|
configSet u c'
|
||||||
|
where
|
||||||
|
config = M.fromList
|
||||||
|
[ ("encryption", "shared")
|
||||||
|
, ("rsyncurl", location)
|
||||||
|
, ("type", "rsync")
|
||||||
|
]
|
||||||
|
|
||||||
|
{- Returns the name of the git remote it created. If there's already a
|
||||||
|
- remote at the location, returns its name. -}
|
||||||
|
makeGitRemote :: String -> String -> Annex String
|
||||||
|
makeGitRemote basename location = makeRemote basename location $ \name ->
|
||||||
|
void $ inRepo $
|
||||||
|
Git.Command.runBool "remote"
|
||||||
|
[Param "add", Param name, Param location]
|
||||||
|
|
||||||
|
{- If there's not already a remote at the location, adds it using the
|
||||||
|
- action, which is passed the name of the remote to make.
|
||||||
|
-
|
||||||
|
- Returns the name of the remote. -}
|
||||||
|
makeRemote :: String -> String -> (String -> Annex ()) -> Annex String
|
||||||
|
makeRemote basename location a = do
|
||||||
|
r <- fromRepo id
|
||||||
|
if (null $ filter samelocation $ Git.remotes r)
|
||||||
|
then do
|
||||||
|
let name = uniqueRemoteName r basename 0
|
||||||
|
a name
|
||||||
|
return name
|
||||||
|
else return basename
|
||||||
|
where
|
||||||
|
samelocation x = Git.repoLocation x == location
|
||||||
|
|
||||||
|
{- Generate an unused name for a remote, adding a number if
|
||||||
|
- necessary. -}
|
||||||
|
uniqueRemoteName :: Git.Repo -> String -> Int -> String
|
||||||
|
uniqueRemoteName r basename n
|
||||||
|
| null namecollision = name
|
||||||
|
| otherwise = uniqueRemoteName r basename (succ n)
|
||||||
|
where
|
||||||
|
namecollision = filter samename (Git.remotes r)
|
||||||
|
samename x = Git.remoteName x == Just name
|
||||||
|
name
|
||||||
|
| n == 0 = basename
|
||||||
|
| otherwise = basename ++ show n
|
|
@ -25,23 +25,24 @@ data PairStage
|
||||||
| PairDone
|
| PairDone
|
||||||
deriving (Eq, Read, Show)
|
deriving (Eq, Read, Show)
|
||||||
|
|
||||||
newtype PairMsg = PairMsg (Verifiable (PairStage, PairData))
|
newtype PairMsg = PairMsg (Verifiable (PairStage, PairData, SomeAddr))
|
||||||
deriving (Eq, Read, Show)
|
deriving (Eq, Read, Show)
|
||||||
|
|
||||||
fromPairMsg :: PairMsg -> (Verifiable (PairStage, PairData))
|
fromPairMsg :: PairMsg -> (Verifiable (PairStage, PairData, SomeAddr))
|
||||||
fromPairMsg (PairMsg m) = m
|
fromPairMsg (PairMsg m) = m
|
||||||
|
|
||||||
pairMsgStage :: PairMsg -> PairStage
|
pairMsgStage :: PairMsg -> PairStage
|
||||||
pairMsgStage (PairMsg (Verifiable (s, _) _)) = s
|
pairMsgStage (PairMsg (Verifiable (s, _, _) _)) = s
|
||||||
|
|
||||||
pairMsgData :: PairMsg -> PairData
|
pairMsgData :: PairMsg -> PairData
|
||||||
pairMsgData (PairMsg (Verifiable (_, d) _)) = d
|
pairMsgData (PairMsg (Verifiable (_, d, _) _)) = d
|
||||||
|
|
||||||
|
pairMsgAddr :: PairMsg -> SomeAddr
|
||||||
|
pairMsgAddr (PairMsg (Verifiable (_, _, a) _)) = a
|
||||||
|
|
||||||
data PairData = PairData
|
data PairData = PairData
|
||||||
-- uname -n output, not a full domain name
|
-- uname -n output, not a full domain name
|
||||||
{ remoteHostName :: Maybe HostName
|
{ remoteHostName :: Maybe HostName
|
||||||
-- the address is included so that it can be verified, avoiding spoofing
|
|
||||||
, remoteAddress :: SomeAddr
|
|
||||||
, remoteUserName :: UserName
|
, remoteUserName :: UserName
|
||||||
, remoteDirectory :: FilePath
|
, remoteDirectory :: FilePath
|
||||||
, remoteSshPubKey :: SshPubKey
|
, remoteSshPubKey :: SshPubKey
|
||||||
|
@ -55,8 +56,9 @@ type UserName = String
|
||||||
- set up on disk. -}
|
- set up on disk. -}
|
||||||
data PairingInProgress = PairingInProgress
|
data PairingInProgress = PairingInProgress
|
||||||
{ inProgressSecret :: Secret
|
{ inProgressSecret :: Secret
|
||||||
, inProgressThreadId :: ThreadId
|
, inProgressThreadId :: Maybe ThreadId
|
||||||
, inProgressSshKeyPair :: SshKeyPair
|
, inProgressSshKeyPair :: SshKeyPair
|
||||||
|
, inProgressPairData :: PairData
|
||||||
}
|
}
|
||||||
|
|
||||||
data SomeAddr = IPv4Addr HostAddress | IPv6Addr HostAddress6
|
data SomeAddr = IPv4Addr HostAddress | IPv6Addr HostAddress6
|
||||||
|
|
81
Assistant/Pairing/MakeRemote.hs
Normal file
81
Assistant/Pairing/MakeRemote.hs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
{- git-annex assistant pairing remote creation
|
||||||
|
-
|
||||||
|
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
||||||
|
-
|
||||||
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Assistant.Pairing.MakeRemote where
|
||||||
|
|
||||||
|
import Assistant.Common
|
||||||
|
import Assistant.ThreadedMonad
|
||||||
|
import Assistant.DaemonStatus
|
||||||
|
import Assistant.ScanRemotes
|
||||||
|
import Assistant.Ssh
|
||||||
|
import Assistant.Pairing
|
||||||
|
import Assistant.Pairing.Network
|
||||||
|
import Assistant.MakeRemote
|
||||||
|
|
||||||
|
import Network.Socket
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
|
{- When pairing is complete, this is used to set up the remote for the host
|
||||||
|
- we paired with. -}
|
||||||
|
finishedPairing :: ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> PairMsg -> SshKeyPair -> IO ()
|
||||||
|
finishedPairing st dstatus scanremotes msg keypair = do
|
||||||
|
sshdata <- setupSshKeyPair keypair =<< pairMsgToSshData msg
|
||||||
|
{- Ensure that we know
|
||||||
|
- the ssh host key for the host we paired with.
|
||||||
|
- If we don't, ssh over to get it. -}
|
||||||
|
unlessM (knownHost $ sshHostName sshdata) $ do
|
||||||
|
void $ sshTranscript
|
||||||
|
[ sshOpt "StrictHostKeyChecking" "no"
|
||||||
|
, sshOpt "NumberOfPasswordPrompts" "0"
|
||||||
|
, "-n"
|
||||||
|
, genSshHost (sshHostName sshdata) (sshUserName sshdata)
|
||||||
|
, "git-annex-shell -c configlist " ++ T.unpack (sshDirectory sshdata)
|
||||||
|
]
|
||||||
|
""
|
||||||
|
makeSshRemote st dstatus scanremotes False sshdata
|
||||||
|
|
||||||
|
{- 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
|
||||||
|
return $ SshData
|
||||||
|
{ sshHostName = T.pack hostname
|
||||||
|
, sshUserName = Just (T.pack $ remoteUserName d)
|
||||||
|
, sshDirectory = T.pack dir
|
||||||
|
, sshRepoName = genSshRepoName hostname dir
|
||||||
|
, needsPubKey = True
|
||||||
|
, rsyncOnly = False
|
||||||
|
}
|
||||||
|
|
||||||
|
{- 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
|
||||||
|
bestHostName msg = case (remoteHostName $ pairMsgData msg) of
|
||||||
|
Just h -> do
|
||||||
|
let localname = h ++ ".local"
|
||||||
|
addrs <- catchDefaultIO (getAddrInfo Nothing (Just localname) Nothing) []
|
||||||
|
maybe fallback (const $ return localname) (headMaybe addrs)
|
||||||
|
Nothing -> fallback
|
||||||
|
where
|
||||||
|
fallback = do
|
||||||
|
let a = pairMsgAddr msg
|
||||||
|
let sockaddr = case a of
|
||||||
|
IPv4Addr addr -> SockAddrInet (PortNum 0) addr
|
||||||
|
IPv6Addr addr -> SockAddrInet6 (PortNum 0) 0 addr 0
|
||||||
|
fromMaybe (showAddr a)
|
||||||
|
<$> catchDefaultIO (fst <$> getNameInfo [] True False sockaddr) Nothing
|
|
@ -1,4 +1,8 @@
|
||||||
{- git-annex assistant pairing network code
|
{- git-annex assistant pairing network code
|
||||||
|
-
|
||||||
|
- All network traffic is sent over multicast UDP. For reliability,
|
||||||
|
- each message is repeated until acknowledged. This is done using a
|
||||||
|
- thread, that gets stopped before the next message is sent.
|
||||||
-
|
-
|
||||||
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
||||||
-
|
-
|
||||||
|
@ -7,15 +11,18 @@
|
||||||
|
|
||||||
module Assistant.Pairing.Network where
|
module Assistant.Pairing.Network where
|
||||||
|
|
||||||
import Common
|
import Assistant.Common
|
||||||
import Assistant.Pairing
|
import Assistant.Pairing
|
||||||
|
import Assistant.DaemonStatus
|
||||||
import Utility.ThreadScheduler
|
import Utility.ThreadScheduler
|
||||||
|
import Utility.Verifiable
|
||||||
|
|
||||||
import Network.Multicast
|
import Network.Multicast
|
||||||
import Network.Info
|
import Network.Info
|
||||||
import Network.Socket
|
import Network.Socket
|
||||||
import Control.Exception (bracket)
|
import Control.Exception (bracket)
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
|
import Control.Concurrent
|
||||||
|
|
||||||
{- This is an arbitrary port in the dynamic port range, that could
|
{- This is an arbitrary port in the dynamic port range, that could
|
||||||
- conceivably be used for some other broadcast messages.
|
- conceivably be used for some other broadcast messages.
|
||||||
|
@ -30,8 +37,9 @@ multicastAddress :: SomeAddr -> HostName
|
||||||
multicastAddress (IPv4Addr _) = "224.0.0.1"
|
multicastAddress (IPv4Addr _) = "224.0.0.1"
|
||||||
multicastAddress (IPv6Addr _) = "ff02::1"
|
multicastAddress (IPv6Addr _) = "ff02::1"
|
||||||
|
|
||||||
{- Multicasts a message repeatedly on all interfaces forever, until killed
|
{- Multicasts a message repeatedly on all interfaces, with a 2 second
|
||||||
- with a 2 second delay between each transmission.
|
- delay between each transmission. The message is repeated forever
|
||||||
|
- unless a number of repeats is specified.
|
||||||
-
|
-
|
||||||
- The remoteHostAddress is set to the interface's IP address.
|
- The remoteHostAddress is set to the interface's IP address.
|
||||||
-
|
-
|
||||||
|
@ -39,15 +47,16 @@ multicastAddress (IPv6Addr _) = "ff02::1"
|
||||||
- but it allows new network interfaces to be used as they come up.
|
- but it allows new network interfaces to be used as they come up.
|
||||||
- On the other hand, the expensive DNS lookups are cached.
|
- On the other hand, the expensive DNS lookups are cached.
|
||||||
-}
|
-}
|
||||||
multicastPairMsg :: (SomeAddr -> PairMsg) -> IO ()
|
multicastPairMsg :: Maybe Int -> Secret -> PairStage -> PairData -> IO ()
|
||||||
multicastPairMsg mkmsg = go M.empty
|
multicastPairMsg repeats secret stage pairdata = go M.empty repeats
|
||||||
where
|
where
|
||||||
go cache = do
|
go _ (Just 0) = noop
|
||||||
|
go cache n = do
|
||||||
addrs <- activeNetworkAddresses
|
addrs <- activeNetworkAddresses
|
||||||
let cache' = updatecache cache addrs
|
let cache' = updatecache cache addrs
|
||||||
mapM_ (sendinterface cache') addrs
|
mapM_ (sendinterface cache') addrs
|
||||||
threadDelaySeconds (Seconds 2)
|
threadDelaySeconds (Seconds 2)
|
||||||
go cache'
|
go cache' $ pred <$> n
|
||||||
sendinterface cache i = void $ catchMaybeIO $
|
sendinterface cache i = void $ catchMaybeIO $
|
||||||
withSocketsDo $ bracket
|
withSocketsDo $ bracket
|
||||||
(multicastSender (multicastAddress i) pairingPort)
|
(multicastSender (multicastAddress i) pairingPort)
|
||||||
|
@ -61,27 +70,23 @@ multicastPairMsg mkmsg = go M.empty
|
||||||
updatecache cache (i:is)
|
updatecache cache (i:is)
|
||||||
| M.member i cache = updatecache cache is
|
| M.member i cache = updatecache cache is
|
||||||
| otherwise = updatecache (M.insert i (show $ mkmsg i) cache) is
|
| otherwise = updatecache (M.insert i (show $ mkmsg i) cache) is
|
||||||
|
mkmsg addr = PairMsg $
|
||||||
|
mkVerifiable (stage, pairdata, addr) secret
|
||||||
|
|
||||||
{- Finds the best hostname to use for the host that sent the PairData.
|
startSending :: DaemonStatusHandle -> PairingInProgress -> IO () -> IO ()
|
||||||
-
|
startSending dstatus pip sender = do
|
||||||
- If remoteHostName is set, tries to use a .local address based on it.
|
tid <- forkIO sender
|
||||||
- That's the most robust, if this system supports .local.
|
let pip' = pip { inProgressThreadId = Just tid }
|
||||||
- Otherwise, looks up the hostname in the DNS for the remoteAddress,
|
oldpip <- modifyDaemonStatus dstatus $
|
||||||
- if any. May fall back to remoteAddress if there's no DNS. Ugh. -}
|
\s -> (s { pairingInProgress = Just pip' }, pairingInProgress s)
|
||||||
bestHostName :: PairData -> IO HostName
|
maybe noop stopold oldpip
|
||||||
bestHostName d = case remoteHostName d of
|
|
||||||
Just h -> do
|
|
||||||
let localname = h ++ ".local"
|
|
||||||
addrs <- catchDefaultIO (getAddrInfo Nothing (Just localname) Nothing) []
|
|
||||||
maybe fallback (const $ return localname) (headMaybe addrs)
|
|
||||||
Nothing -> fallback
|
|
||||||
where
|
where
|
||||||
fallback = do
|
stopold = maybe noop killThread . inProgressThreadId
|
||||||
let sockaddr = case remoteAddress d of
|
|
||||||
IPv4Addr a -> SockAddrInet (PortNum 0) a
|
stopSending :: DaemonStatusHandle -> PairingInProgress -> IO ()
|
||||||
IPv6Addr a -> SockAddrInet6 (PortNum 0) 0 a 0
|
stopSending dstatus pip = do
|
||||||
fromMaybe (show $ remoteAddress d)
|
maybe noop killThread $ inProgressThreadId pip
|
||||||
<$> catchDefaultIO (fst <$> getNameInfo [] True False sockaddr) Nothing
|
modifyDaemonStatus_ dstatus $ \s -> s { pairingInProgress = Nothing }
|
||||||
|
|
||||||
class ToSomeAddr a where
|
class ToSomeAddr a where
|
||||||
toSomeAddr :: a -> SomeAddr
|
toSomeAddr :: a -> SomeAddr
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
module Assistant.Ssh where
|
module Assistant.Ssh where
|
||||||
|
|
||||||
import Common
|
import Common.Annex
|
||||||
import Utility.TempFile
|
import Utility.TempFile
|
||||||
|
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
|
@ -43,6 +43,10 @@ sshDir = do
|
||||||
home <- myHomeDir
|
home <- myHomeDir
|
||||||
return $ home </> ".ssh"
|
return $ home </> ".ssh"
|
||||||
|
|
||||||
|
{- user@host or host -}
|
||||||
|
genSshHost :: Text -> Maybe Text -> String
|
||||||
|
genSshHost host user = maybe "" (\v -> T.unpack v ++ "@") user ++ T.unpack host
|
||||||
|
|
||||||
{- host_dir, with all / in dir replaced by _, and bad characters removed -}
|
{- host_dir, with all / in dir replaced by _, and bad characters removed -}
|
||||||
genSshRepoName :: String -> FilePath -> String
|
genSshRepoName :: String -> FilePath -> String
|
||||||
genSshRepoName host dir
|
genSshRepoName host dir
|
||||||
|
@ -171,3 +175,12 @@ setupSshKeyPair sshkeypair sshdata = do
|
||||||
sshpubkeyfile = sshprivkeyfile ++ ".pub"
|
sshpubkeyfile = sshprivkeyfile ++ ".pub"
|
||||||
mangledhost = "git-annex-" ++ T.unpack (sshHostName sshdata) ++ user
|
mangledhost = "git-annex-" ++ T.unpack (sshHostName sshdata) ++ user
|
||||||
user = maybe "" (\u -> "-" ++ T.unpack u) (sshUserName sshdata)
|
user = maybe "" (\u -> "-" ++ T.unpack u) (sshUserName sshdata)
|
||||||
|
|
||||||
|
{- Does ssh have known_hosts data for a hostname? -}
|
||||||
|
knownHost :: Text -> IO Bool
|
||||||
|
knownHost hostname = do
|
||||||
|
sshdir <- sshDir
|
||||||
|
ifM (doesFileExist $ sshdir </> "known_hosts")
|
||||||
|
( not . null <$> readProcess "ssh-keygen" ["-F", T.unpack hostname]
|
||||||
|
, return False
|
||||||
|
)
|
||||||
|
|
|
@ -24,6 +24,7 @@ import qualified Annex.Branch
|
||||||
|
|
||||||
import Data.Time.Clock
|
import Data.Time.Clock
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
|
import Control.Concurrent
|
||||||
|
|
||||||
{- Syncs with remotes that may have been disconnected for a while.
|
{- Syncs with remotes that may have been disconnected for a while.
|
||||||
-
|
-
|
||||||
|
@ -108,3 +109,9 @@ manualPull st currentbranch remotes = do
|
||||||
forM_ remotes $ \r ->
|
forM_ remotes $ \r ->
|
||||||
runThreadState st $ Command.Sync.mergeRemote r currentbranch
|
runThreadState st $ Command.Sync.mergeRemote r currentbranch
|
||||||
return haddiverged
|
return haddiverged
|
||||||
|
|
||||||
|
{- Start syncing a newly added remote, using a background thread. -}
|
||||||
|
syncNewRemote :: ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> Remote -> IO ()
|
||||||
|
syncNewRemote st dstatus scanremotes remote = do
|
||||||
|
runThreadState st $ updateKnownRemotes dstatus
|
||||||
|
void $ forkIO $ do reconnectRemotes "SyncRemote" st dstatus scanremotes [remote]
|
||||||
|
|
|
@ -10,7 +10,9 @@ module Assistant.Threads.PairListener where
|
||||||
import Assistant.Common
|
import Assistant.Common
|
||||||
import Assistant.Pairing
|
import Assistant.Pairing
|
||||||
import Assistant.Pairing.Network
|
import Assistant.Pairing.Network
|
||||||
|
import Assistant.Pairing.MakeRemote
|
||||||
import Assistant.ThreadedMonad
|
import Assistant.ThreadedMonad
|
||||||
|
import Assistant.ScanRemotes
|
||||||
import Assistant.DaemonStatus
|
import Assistant.DaemonStatus
|
||||||
import Assistant.WebApp
|
import Assistant.WebApp
|
||||||
import Assistant.WebApp.Types
|
import Assistant.WebApp.Types
|
||||||
|
@ -25,8 +27,8 @@ import qualified Data.Text as T
|
||||||
thisThread :: ThreadName
|
thisThread :: ThreadName
|
||||||
thisThread = "PairListener"
|
thisThread = "PairListener"
|
||||||
|
|
||||||
pairListenerThread :: ThreadState -> DaemonStatusHandle -> UrlRenderer -> NamedThread
|
pairListenerThread :: ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> UrlRenderer -> NamedThread
|
||||||
pairListenerThread st dstatus urlrenderer = thread $ withSocketsDo $ do
|
pairListenerThread st dstatus scanremotes urlrenderer = thread $ withSocketsDo $ do
|
||||||
sock <- multicastReceiver (multicastAddress $ IPv4Addr undefined) pairingPort
|
sock <- multicastReceiver (multicastAddress $ IPv4Addr undefined) pairingPort
|
||||||
go sock
|
go sock
|
||||||
where
|
where
|
||||||
|
@ -47,15 +49,16 @@ pairListenerThread st dstatus urlrenderer = thread $ withSocketsDo $ do
|
||||||
|
|
||||||
dispatch Nothing = noop
|
dispatch Nothing = noop
|
||||||
dispatch (Just m@(PairMsg v)) = do
|
dispatch (Just m@(PairMsg v)) = do
|
||||||
verified <- maybe False (verify v . inProgressSecret)
|
pip <- pairingInProgress <$> getDaemonStatus dstatus
|
||||||
. pairingInProgress
|
let verified = maybe False (verify v . inProgressSecret) pip
|
||||||
<$> getDaemonStatus dstatus
|
|
||||||
case pairMsgStage m of
|
case pairMsgStage m of
|
||||||
PairReq -> pairReqReceived verified dstatus urlrenderer m
|
PairReq -> pairReqReceived verified dstatus urlrenderer m
|
||||||
PairAck -> pairAckReceived verified dstatus m
|
PairAck -> pairAckReceived verified pip st dstatus scanremotes m
|
||||||
PairDone -> pairDoneReceived verified dstatus m
|
PairDone -> pairDoneReceived verified pip st dstatus scanremotes m
|
||||||
|
|
||||||
{- Pair request alerts from the same host combine,
|
{- Show an alert when a PairReq is seen.
|
||||||
|
-
|
||||||
|
- Pair request alerts from the same host combine,
|
||||||
- so repeated requests do not add additional alerts. -}
|
- so repeated requests do not add additional alerts. -}
|
||||||
pairReqReceived :: Bool -> DaemonStatusHandle -> UrlRenderer -> PairMsg -> IO ()
|
pairReqReceived :: Bool -> DaemonStatusHandle -> UrlRenderer -> PairMsg -> IO ()
|
||||||
pairReqReceived True _ _ _ = noop -- ignore out own PairReq
|
pairReqReceived True _ _ _ = noop -- ignore out own PairReq
|
||||||
|
@ -69,12 +72,11 @@ pairReqReceived False dstatus urlrenderer msg = do
|
||||||
, buttonAction = Just onclick
|
, buttonAction = Just onclick
|
||||||
}
|
}
|
||||||
where
|
where
|
||||||
v = fromPairMsg msg
|
pairdata = pairMsgData msg
|
||||||
(_, pairdata) = verifiableVal v
|
|
||||||
repo = concat
|
repo = concat
|
||||||
[ remoteUserName pairdata
|
[ remoteUserName pairdata
|
||||||
, "@"
|
, "@"
|
||||||
, fromMaybe (showAddr $ remoteAddress pairdata)
|
, fromMaybe (showAddr $ pairMsgAddr msg)
|
||||||
(remoteHostName pairdata)
|
(remoteHostName pairdata)
|
||||||
, ":"
|
, ":"
|
||||||
, (remoteDirectory pairdata)
|
, (remoteDirectory pairdata)
|
||||||
|
@ -90,27 +92,34 @@ pairReqReceived False dstatus urlrenderer msg = do
|
||||||
, alertData = [UnTensed $ T.pack $ "pair request with " ++ repo ++ " in progress"]
|
, alertData = [UnTensed $ T.pack $ "pair request with " ++ repo ++ " in progress"]
|
||||||
}
|
}
|
||||||
|
|
||||||
{- When a valid PairAck is seen, a host has successfully paired with
|
{- When a verified PairAck is seen, a host is ready to pair with us, and has
|
||||||
- us, and we should finish pairing with them. Then send a single PairDone.
|
- already configured our ssh key. Stop sending PairReqs, finish the pairing,
|
||||||
|
- and send a few PairDones.
|
||||||
-
|
-
|
||||||
- A stale PairAck might also be seen, after we've finished pairing.
|
- TODO: A stale PairAck might also be seen, after we've finished pairing.
|
||||||
- Perhaps our PairDone was not received. To handle this, we keep
|
- Perhaps our PairDone was not received. To handle this, we keep
|
||||||
- a list of recently finished pairings, and re-send PairDone in
|
- a list of recently finished pairings, and re-send PairDone in
|
||||||
- response to stale PairAcks for them.
|
- response to stale PairAcks for them.
|
||||||
-}
|
-}
|
||||||
pairAckReceived :: Bool -> DaemonStatusHandle -> PairMsg -> IO ()
|
pairAckReceived :: Bool -> Maybe PairingInProgress -> ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> PairMsg -> IO ()
|
||||||
pairAckReceived False _ _ = noop -- not verified
|
pairAckReceived False _ _ _ _ _ = noop -- not verified
|
||||||
pairAckReceived True dstatus msg = error "TODO"
|
pairAckReceived True Nothing _ _ _ _ = noop -- not in progress
|
||||||
|
pairAckReceived True (Just pip) st dstatus scanremotes msg = do
|
||||||
|
stopSending dstatus pip
|
||||||
|
finishedPairing st dstatus scanremotes msg (inProgressSshKeyPair pip)
|
||||||
|
startSending dstatus pip $ multicastPairMsg
|
||||||
|
(Just 10) (inProgressSecret pip) PairDone (inProgressPairData pip)
|
||||||
|
|
||||||
{- If we get a valid PairDone, and are sending PairAcks, we can stop
|
{- If we get a verified PairDone, the host has accepted our PairAck, and
|
||||||
- sending them, as the message has been received.
|
- has paired with us. Stop sending PairAcks, and finish pairing with them.
|
||||||
-
|
-
|
||||||
- Also, now is the time to remove the pair request alert, as pairing is
|
- TODO: Should third-party hosts remove their pair request alert when they
|
||||||
- over. Do that even if the PairDone cannot be validated, as we might
|
- see a PairDone? How to tell if a PairDone matches with the PairReq
|
||||||
- be a third host that did not participate in the pairing.
|
- that brought up the alert? Cannot verify it without the secret..
|
||||||
- Note: This does allow a bad actor to squelch pairing on a network
|
|
||||||
- by sending bogus PairDones.
|
|
||||||
-}
|
-}
|
||||||
pairDoneReceived :: Bool -> DaemonStatusHandle -> PairMsg -> IO ()
|
pairDoneReceived :: Bool -> Maybe PairingInProgress -> ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> PairMsg -> IO ()
|
||||||
pairDoneReceived False _ _ = noop -- not verified
|
pairDoneReceived False _ _ _ _ _ = noop -- not verified
|
||||||
pairDoneReceived True dstatus msg = error "TODO"
|
pairDoneReceived True Nothing _ _ _ _ = noop -- not in progress
|
||||||
|
pairDoneReceived True (Just pip) st dstatus scanremotes msg = do
|
||||||
|
stopSending dstatus pip
|
||||||
|
finishedPairing st dstatus scanremotes msg (inProgressSshKeyPair pip)
|
||||||
|
|
|
@ -14,15 +14,12 @@ import Assistant.WebApp
|
||||||
import Assistant.WebApp.Types
|
import Assistant.WebApp.Types
|
||||||
import Assistant.WebApp.SideBar
|
import Assistant.WebApp.SideBar
|
||||||
import Assistant.Sync
|
import Assistant.Sync
|
||||||
import Assistant.DaemonStatus
|
import Assistant.MakeRemote
|
||||||
import Utility.Yesod
|
import Utility.Yesod
|
||||||
import Remote.List
|
|
||||||
import qualified Remote
|
|
||||||
import Init
|
import Init
|
||||||
import qualified Git
|
import qualified Git
|
||||||
import qualified Git.Construct
|
import qualified Git.Construct
|
||||||
import qualified Git.Config
|
import qualified Git.Config
|
||||||
import qualified Git.Command
|
|
||||||
import qualified Annex
|
import qualified Annex
|
||||||
import Locations.UserConfig
|
import Locations.UserConfig
|
||||||
import Utility.FreeDesktop
|
import Utility.FreeDesktop
|
||||||
|
@ -37,7 +34,6 @@ import qualified Data.Text as T
|
||||||
import Data.Char
|
import Data.Char
|
||||||
import System.Posix.Directory
|
import System.Posix.Directory
|
||||||
import qualified Control.Exception as E
|
import qualified Control.Exception as E
|
||||||
import Control.Concurrent
|
|
||||||
|
|
||||||
data RepositoryPath = RepositoryPath Text
|
data RepositoryPath = RepositoryPath Text
|
||||||
deriving Show
|
deriving Show
|
||||||
|
@ -198,61 +194,15 @@ getAddDriveR = bootstrap (Just Config) $ do
|
||||||
void $ makeGitRemote hostname hostlocation
|
void $ makeGitRemote hostname hostlocation
|
||||||
addRemote $ makeGitRemote name dir
|
addRemote $ makeGitRemote name dir
|
||||||
|
|
||||||
{- Runs an action that returns a name of the remote, and finishes adding it. -}
|
|
||||||
addRemote :: Annex String -> Annex Remote
|
|
||||||
addRemote a = do
|
|
||||||
name <- a
|
|
||||||
void $ remoteListRefresh
|
|
||||||
maybe (error "failed to add remote") return =<< Remote.byName (Just name)
|
|
||||||
|
|
||||||
{- Returns the name of the git remote it created. If there's already a
|
|
||||||
- remote at the location, returns its name. -}
|
|
||||||
makeGitRemote :: String -> String -> Annex String
|
|
||||||
makeGitRemote basename location = makeRemote basename location $ \name ->
|
|
||||||
void $ inRepo $
|
|
||||||
Git.Command.runBool "remote"
|
|
||||||
[Param "add", Param name, Param location]
|
|
||||||
|
|
||||||
{- If there's not already a remote at the location, adds it using the
|
|
||||||
- action, which is passed the name of the remote to make.
|
|
||||||
-
|
|
||||||
- Returns the name of the remote. -}
|
|
||||||
makeRemote :: String -> String -> (String -> Annex ()) -> Annex String
|
|
||||||
makeRemote basename location a = do
|
|
||||||
r <- fromRepo id
|
|
||||||
if (null $ filter samelocation $ Git.remotes r)
|
|
||||||
then do
|
|
||||||
let name = uniqueRemoteName r basename 0
|
|
||||||
a name
|
|
||||||
return name
|
|
||||||
else return basename
|
|
||||||
where
|
|
||||||
samelocation x = Git.repoLocation x == location
|
|
||||||
|
|
||||||
{- Generate an unused name for a remote, adding a number if
|
|
||||||
- necessary. -}
|
|
||||||
uniqueRemoteName :: Git.Repo -> String -> Int -> String
|
|
||||||
uniqueRemoteName r basename n
|
|
||||||
| null namecollision = name
|
|
||||||
| otherwise = uniqueRemoteName r basename (succ n)
|
|
||||||
where
|
|
||||||
namecollision = filter samename (Git.remotes r)
|
|
||||||
samename x = Git.remoteName x == Just name
|
|
||||||
name
|
|
||||||
| n == 0 = basename
|
|
||||||
| otherwise = basename ++ show n
|
|
||||||
|
|
||||||
{- Start syncing a newly added remote, using a background thread. -}
|
{- Start syncing a newly added remote, using a background thread. -}
|
||||||
syncRemote :: Remote -> Handler ()
|
syncRemote :: Remote -> Handler ()
|
||||||
syncRemote remote = do
|
syncRemote remote = do
|
||||||
webapp <- getYesod
|
webapp <- getYesod
|
||||||
runAnnex () $ updateKnownRemotes (daemonStatus webapp)
|
liftIO $ syncNewRemote
|
||||||
void $ liftIO $ forkIO $ do
|
(fromJust $ threadState webapp)
|
||||||
reconnectRemotes "WebApp"
|
(daemonStatus webapp)
|
||||||
(fromJust $ threadState webapp)
|
(scanRemotes webapp)
|
||||||
(daemonStatus webapp)
|
remote
|
||||||
(scanRemotes webapp)
|
|
||||||
[remote]
|
|
||||||
|
|
||||||
{- List of removable drives. -}
|
{- List of removable drives. -}
|
||||||
driveList :: IO [RemovableDrive]
|
driveList :: IO [RemovableDrive]
|
||||||
|
|
|
@ -39,7 +39,6 @@ import Utility.Yesod
|
||||||
import Assistant.Common
|
import Assistant.Common
|
||||||
import Assistant.Pairing.Network
|
import Assistant.Pairing.Network
|
||||||
import Assistant.Ssh
|
import Assistant.Ssh
|
||||||
import qualified Assistant.WebApp.Configurators.Ssh as Ssh
|
|
||||||
import Assistant.Alert
|
import Assistant.Alert
|
||||||
import Assistant.DaemonStatus
|
import Assistant.DaemonStatus
|
||||||
import Utility.Verifiable
|
import Utility.Verifiable
|
||||||
|
@ -60,9 +59,7 @@ import Control.Concurrent
|
||||||
|
|
||||||
getStartPairR :: Handler RepHtml
|
getStartPairR :: Handler RepHtml
|
||||||
#ifdef WITH_PAIRING
|
#ifdef WITH_PAIRING
|
||||||
getStartPairR = do
|
getStartPairR = promptSecret Nothing $ startPairing PairReq noop
|
||||||
keypair <- liftIO genSshKeyPair
|
|
||||||
promptSecret Nothing $ startPairing PairReq keypair noop
|
|
||||||
#else
|
#else
|
||||||
getStartPairR = noPairing
|
getStartPairR = noPairing
|
||||||
#endif
|
#endif
|
||||||
|
@ -70,44 +67,19 @@ getStartPairR = noPairing
|
||||||
getFinishPairR :: PairMsg -> Handler RepHtml
|
getFinishPairR :: PairMsg -> Handler RepHtml
|
||||||
#ifdef WITH_PAIRING
|
#ifdef WITH_PAIRING
|
||||||
getFinishPairR msg = promptSecret (Just msg) $ \_ secret -> do
|
getFinishPairR msg = promptSecret (Just msg) $ \_ secret -> do
|
||||||
keypair <- setup
|
setup
|
||||||
startPairing PairAck keypair cleanup "" secret
|
startPairing PairAck cleanup "" secret
|
||||||
where
|
where
|
||||||
pubkey = remoteSshPubKey $ pairMsgData msg
|
pubkey = remoteSshPubKey $ pairMsgData msg
|
||||||
setup = do
|
setup = do
|
||||||
validateSshPubKey pubKey
|
liftIO $ validateSshPubKey pubkey
|
||||||
unlessM (liftIO $ makeAuthorizedKeys False pubkey) $
|
unlessM (liftIO $ makeAuthorizedKeys False pubkey) $
|
||||||
error "failed setting up ssh authorized keys"
|
error "failed setting up ssh authorized keys"
|
||||||
keypair <- liftIO genSshKeyPair
|
|
||||||
sshdata <- liftIO $ pairMsgToSshData msg
|
|
||||||
sshdata' <- liftIO $ setupSshKeyPair keypair sshdata
|
|
||||||
void $ lift $ Ssh.makeSshWithKeyPair False sshdata' (Just keypair)
|
|
||||||
return keypair
|
|
||||||
cleanup = error "TODO clean up authorized keys and generated ssh key and remove git remote"
|
cleanup = error "TODO clean up authorized keys and generated ssh key and remove git remote"
|
||||||
#else
|
#else
|
||||||
getFinishPairR _ = noPairing
|
getFinishPairR _ = noPairing
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
{- 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 d
|
|
||||||
let dir = case remoteDirectory d of
|
|
||||||
('~':'/':v) -> v
|
|
||||||
v -> v
|
|
||||||
return $ SshData
|
|
||||||
{ sshHostName = T.pack hostname
|
|
||||||
, sshUserName = Just (T.pack $ remoteUserName d)
|
|
||||||
, sshDirectory = T.pack dir
|
|
||||||
, sshRepoName = genSshRepoName besthostname dir
|
|
||||||
, needsPubKey = True
|
|
||||||
, rsyncOnly = False
|
|
||||||
}
|
|
||||||
|
|
||||||
getInprogressPairR :: Text -> Handler RepHtml
|
getInprogressPairR :: Text -> Handler RepHtml
|
||||||
#ifdef WITH_PAIRING
|
#ifdef WITH_PAIRING
|
||||||
getInprogressPairR secret = pairPage $ do
|
getInprogressPairR secret = pairPage $ do
|
||||||
|
@ -127,27 +99,23 @@ getInprogressPairR _ = noPairing
|
||||||
-
|
-
|
||||||
- Redirects to the pairing in progress page.
|
- Redirects to the pairing in progress page.
|
||||||
-}
|
-}
|
||||||
startPairing :: PairStage -> SshKeyPair -> IO () -> Text -> Secret -> Widget
|
startPairing :: PairStage -> IO () -> Text -> Secret -> Widget
|
||||||
startPairing stage keypair oncancel displaysecret secret = do
|
startPairing stage oncancel displaysecret secret = do
|
||||||
|
keypair <- liftIO $ genSshKeyPair
|
||||||
dstatus <- daemonStatus <$> lift getYesod
|
dstatus <- daemonStatus <$> lift getYesod
|
||||||
urlrender <- lift getUrlRender
|
urlrender <- lift getUrlRender
|
||||||
let homeurl = urlrender HomeR
|
let homeurl = urlrender HomeR
|
||||||
sender <- mksender
|
pairdata <- PairData
|
||||||
|
<$> liftIO getHostname
|
||||||
|
<*> liftIO getUserName
|
||||||
|
<*> (fromJust . relDir <$> lift getYesod)
|
||||||
|
<*> pure (sshPubKey keypair)
|
||||||
liftIO $ do
|
liftIO $ do
|
||||||
pip <- PairingInProgress secret
|
let sender = multicastPairMsg Nothing secret stage pairdata
|
||||||
<$> sendrequests sender dstatus homeurl
|
let pip = PairingInProgress secret Nothing keypair pairdata
|
||||||
<*> pure keypair
|
startSending dstatus pip $ sendrequests sender dstatus homeurl
|
||||||
oldpip <- modifyDaemonStatus dstatus $
|
|
||||||
\s -> (s { pairingInProgress = Just pip }, pairingInProgress s)
|
|
||||||
maybe noop stopold oldpip
|
|
||||||
lift $ redirect $ InprogressPairR displaysecret
|
lift $ redirect $ InprogressPairR displaysecret
|
||||||
where
|
where
|
||||||
mksender = do
|
|
||||||
hostname <- liftIO getHostname
|
|
||||||
username <- liftIO getUserName
|
|
||||||
reldir <- fromJust . relDir <$> lift getYesod
|
|
||||||
return $ multicastPairMsg $ \addr -> PairMsg $ mkVerifiable
|
|
||||||
(stage, PairData hostname addr username reldir (sshPubKey keypair)) secret
|
|
||||||
{- Sends pairing messages until the thread is killed,
|
{- Sends pairing messages until the thread is killed,
|
||||||
- and shows an activity alert while doing it.
|
- and shows an activity alert while doing it.
|
||||||
-
|
-
|
||||||
|
@ -156,7 +124,7 @@ startPairing stage keypair oncancel displaysecret secret = do
|
||||||
- have been on a page specific to the in-process pairing
|
- have been on a page specific to the in-process pairing
|
||||||
- that just stopped, so can't go back there.
|
- that just stopped, so can't go back there.
|
||||||
-}
|
-}
|
||||||
sendrequests sender dstatus homeurl = forkIO $ do
|
sendrequests sender dstatus homeurl = do
|
||||||
tid <- myThreadId
|
tid <- myThreadId
|
||||||
let selfdestruct = AlertButton
|
let selfdestruct = AlertButton
|
||||||
{ buttonLabel = "Cancel"
|
{ buttonLabel = "Cancel"
|
||||||
|
@ -168,7 +136,6 @@ startPairing stage keypair oncancel displaysecret secret = do
|
||||||
alertDuring dstatus (pairingAlert selfdestruct) $ do
|
alertDuring dstatus (pairingAlert selfdestruct) $ do
|
||||||
_ <- E.try sender :: IO (Either E.SomeException ())
|
_ <- E.try sender :: IO (Either E.SomeException ())
|
||||||
return ()
|
return ()
|
||||||
stopold = killThread . inProgressThreadId
|
|
||||||
|
|
||||||
data InputSecret = InputSecret { secretText :: Maybe Text }
|
data InputSecret = InputSecret { secretText :: Maybe Text }
|
||||||
|
|
||||||
|
@ -200,7 +167,7 @@ promptSecret msg cont = pairPage $ do
|
||||||
let badphrase = isJust mproblem
|
let badphrase = isJust mproblem
|
||||||
let problem = fromMaybe "" mproblem
|
let problem = fromMaybe "" mproblem
|
||||||
let (username, hostname) = maybe ("", "")
|
let (username, hostname) = maybe ("", "")
|
||||||
(\(_, v) -> (T.pack $ remoteUserName v, T.pack $ fromMaybe (showAddr $ remoteAddress v) (remoteHostName v)))
|
(\(_, v, a) -> (T.pack $ remoteUserName v, T.pack $ fromMaybe (showAddr a) (remoteHostName v)))
|
||||||
(verifiableVal . fromPairMsg <$> msg)
|
(verifiableVal . fromPairMsg <$> msg)
|
||||||
u <- T.pack <$> liftIO getUserName
|
u <- T.pack <$> liftIO getUserName
|
||||||
let sameusername = username == u
|
let sameusername = username == u
|
||||||
|
|
|
@ -11,21 +11,15 @@ module Assistant.WebApp.Configurators.Ssh where
|
||||||
|
|
||||||
import Assistant.Common
|
import Assistant.Common
|
||||||
import Assistant.Ssh
|
import Assistant.Ssh
|
||||||
|
import Assistant.MakeRemote
|
||||||
import Assistant.WebApp
|
import Assistant.WebApp
|
||||||
import Assistant.WebApp.Types
|
import Assistant.WebApp.Types
|
||||||
import Assistant.WebApp.SideBar
|
import Assistant.WebApp.SideBar
|
||||||
import Utility.Yesod
|
import Utility.Yesod
|
||||||
import Assistant.WebApp.Configurators.Local
|
|
||||||
import qualified Types.Remote as R
|
|
||||||
import qualified Remote.Rsync as Rsync
|
|
||||||
import qualified Command.InitRemote
|
|
||||||
import Logs.UUID
|
|
||||||
import Logs.Remote
|
|
||||||
|
|
||||||
import Yesod
|
import Yesod
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Data.Map as M
|
|
||||||
import Network.BSD
|
import Network.BSD
|
||||||
import System.Posix.User
|
import System.Posix.User
|
||||||
|
|
||||||
|
@ -127,7 +121,7 @@ getAddSshR = sshConfigurator $ do
|
||||||
testServer :: SshServer -> IO (ServerStatus, Bool)
|
testServer :: SshServer -> IO (ServerStatus, Bool)
|
||||||
testServer (SshServer { hostname = Nothing }) = return
|
testServer (SshServer { hostname = Nothing }) = return
|
||||||
(UnusableServer "Please enter a host name.", False)
|
(UnusableServer "Please enter a host name.", False)
|
||||||
testServer sshserver = do
|
testServer sshserver@(SshServer { hostname = Just hn }) = do
|
||||||
status <- probe [sshOpt "NumberOfPasswordPrompts" "0"]
|
status <- probe [sshOpt "NumberOfPasswordPrompts" "0"]
|
||||||
if usable status
|
if usable status
|
||||||
then return (status, False)
|
then return (status, False)
|
||||||
|
@ -141,7 +135,7 @@ testServer sshserver = do
|
||||||
, checkcommand "git-annex-shell"
|
, checkcommand "git-annex-shell"
|
||||||
, checkcommand "rsync"
|
, checkcommand "rsync"
|
||||||
]
|
]
|
||||||
knownhost <- knownHost sshserver
|
knownhost <- knownHost hn
|
||||||
let sshopts = filter (not . null) $ extraopts ++
|
let sshopts = filter (not . null) $ extraopts ++
|
||||||
{- If this is an already known host, let
|
{- If this is an already known host, let
|
||||||
- ssh check it as usual.
|
- ssh check it as usual.
|
||||||
|
@ -165,10 +159,6 @@ testServer sshserver = do
|
||||||
token r = "git-annex-probe " ++ r
|
token r = "git-annex-probe " ++ r
|
||||||
report r = "echo " ++ token r
|
report r = "echo " ++ token r
|
||||||
|
|
||||||
{- user@host or host -}
|
|
||||||
genSshHost :: Text -> Maybe Text -> String
|
|
||||||
genSshHost host user = maybe "" (\v -> T.unpack v ++ "@") user ++ T.unpack host
|
|
||||||
|
|
||||||
{- Runs a ssh command; if it fails shows the user the transcript,
|
{- Runs a ssh command; if it fails shows the user the transcript,
|
||||||
- and if it succeeds, runs an action. -}
|
- and if it succeeds, runs an action. -}
|
||||||
sshSetup :: [String] -> String -> Handler RepHtml -> Handler RepHtml
|
sshSetup :: [String] -> String -> Handler RepHtml -> Handler RepHtml
|
||||||
|
@ -182,16 +172,6 @@ showSshErr :: String -> Handler RepHtml
|
||||||
showSshErr msg = sshConfigurator $
|
showSshErr msg = sshConfigurator $
|
||||||
$(widgetFile "configurators/ssh/error")
|
$(widgetFile "configurators/ssh/error")
|
||||||
|
|
||||||
{- Does ssh have known_hosts data for a hostname? -}
|
|
||||||
knownHost :: SshServer -> IO Bool
|
|
||||||
knownHost (SshServer { hostname = Nothing }) = return False
|
|
||||||
knownHost (SshServer { hostname = Just h }) = do
|
|
||||||
sshdir <- sshDir
|
|
||||||
ifM (doesFileExist $ sshdir </> "known_hosts")
|
|
||||||
( not . null <$> readProcess "ssh-keygen" ["-F", T.unpack h]
|
|
||||||
, return False
|
|
||||||
)
|
|
||||||
|
|
||||||
getConfirmSshR :: SshData -> Handler RepHtml
|
getConfirmSshR :: SshData -> Handler RepHtml
|
||||||
getConfirmSshR sshdata = sshConfigurator $ do
|
getConfirmSshR sshdata = sshConfigurator $ do
|
||||||
let authtoken = webAppFormAuthToken
|
let authtoken = webAppFormAuthToken
|
||||||
|
@ -208,11 +188,11 @@ makeSsh rsync sshdata
|
||||||
| needsPubKey sshdata = do
|
| needsPubKey sshdata = do
|
||||||
keypair <- liftIO $ genSshKeyPair
|
keypair <- liftIO $ genSshKeyPair
|
||||||
sshdata' <- liftIO $ setupSshKeyPair keypair sshdata
|
sshdata' <- liftIO $ setupSshKeyPair keypair sshdata
|
||||||
makeSshWithKeyPair rsync sshdata' (Just keypair)
|
makeSsh' rsync sshdata' (Just keypair)
|
||||||
| otherwise = makeSshWithKeyPair rsync sshdata Nothing
|
| otherwise = makeSsh' rsync sshdata Nothing
|
||||||
|
|
||||||
makeSshWithKeyPair :: Bool -> SshData -> Maybe SshKeyPair -> Handler RepHtml
|
makeSsh' :: Bool -> SshData -> Maybe SshKeyPair -> Handler RepHtml
|
||||||
makeSshWithKeyPair rsync sshdata keypair =
|
makeSsh' rsync sshdata keypair =
|
||||||
sshSetup [sshhost, remoteCommand] "" $
|
sshSetup [sshhost, remoteCommand] "" $
|
||||||
makeSshRepo rsync sshdata
|
makeSshRepo rsync sshdata
|
||||||
where
|
where
|
||||||
|
@ -230,40 +210,13 @@ makeSshWithKeyPair rsync sshdata keypair =
|
||||||
|
|
||||||
makeSshRepo :: Bool -> SshData -> Handler RepHtml
|
makeSshRepo :: Bool -> SshData -> Handler RepHtml
|
||||||
makeSshRepo forcersync sshdata = do
|
makeSshRepo forcersync sshdata = do
|
||||||
r <- runAnnex undefined $
|
webapp <- getYesod
|
||||||
addRemote $ maker (sshRepoName sshdata) sshurl
|
liftIO $ makeSshRemote
|
||||||
syncRemote r
|
(fromJust $ threadState webapp)
|
||||||
|
(daemonStatus webapp)
|
||||||
|
(scanRemotes webapp)
|
||||||
|
forcersync sshdata
|
||||||
redirect RepositoriesR
|
redirect RepositoriesR
|
||||||
where
|
|
||||||
rsync = forcersync || rsyncOnly sshdata
|
|
||||||
maker
|
|
||||||
| rsync = makeRsyncRemote
|
|
||||||
| otherwise = makeGitRemote
|
|
||||||
sshurl = T.unpack $ T.concat $
|
|
||||||
if rsync
|
|
||||||
then [u, h, ":", sshDirectory sshdata, "/"]
|
|
||||||
else ["ssh://", u, h, d, "/"]
|
|
||||||
where
|
|
||||||
u = maybe "" (\v -> T.concat [v, "@"]) $ sshUserName sshdata
|
|
||||||
h = sshHostName sshdata
|
|
||||||
d
|
|
||||||
| "/" `T.isPrefixOf` sshDirectory sshdata = d
|
|
||||||
| otherwise = T.concat ["/~/", sshDirectory sshdata]
|
|
||||||
|
|
||||||
|
|
||||||
{- Inits a rsync special remote, and returns the name of the remote. -}
|
|
||||||
makeRsyncRemote :: String -> String -> Annex String
|
|
||||||
makeRsyncRemote name location = makeRemote name location $ const $ do
|
|
||||||
(u, c) <- Command.InitRemote.findByName name
|
|
||||||
c' <- R.setup Rsync.remote u $ M.union config c
|
|
||||||
describeUUID u name
|
|
||||||
configSet u c'
|
|
||||||
where
|
|
||||||
config = M.fromList
|
|
||||||
[ ("encryption", "shared")
|
|
||||||
, ("rsyncurl", location)
|
|
||||||
, ("type", "rsync")
|
|
||||||
]
|
|
||||||
|
|
||||||
getAddRsyncNetR :: Handler RepHtml
|
getAddRsyncNetR :: Handler RepHtml
|
||||||
getAddRsyncNetR = do
|
getAddRsyncNetR = do
|
||||||
|
@ -276,7 +229,7 @@ getAddRsyncNetR = do
|
||||||
$(widgetFile "configurators/addrsync.net")
|
$(widgetFile "configurators/addrsync.net")
|
||||||
case result of
|
case result of
|
||||||
FormSuccess sshserver -> do
|
FormSuccess sshserver -> do
|
||||||
knownhost <- liftIO $ knownHost sshserver
|
knownhost <- liftIO $ maybe (return False) knownHost (hostname sshserver)
|
||||||
keypair <- liftIO $ genSshKeyPair
|
keypair <- liftIO $ genSshKeyPair
|
||||||
sshdata <- liftIO $ setupSshKeyPair keypair
|
sshdata <- liftIO $ setupSshKeyPair keypair
|
||||||
(mkSshData sshserver)
|
(mkSshData sshserver)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue