2013-11-23 04:54:08 +00:00
|
|
|
{- git-annex assistant upgrading
|
|
|
|
-
|
|
|
|
- Copyright 2013 Joey Hess <joey@kitenet.net>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
2013-11-24 03:45:49 +00:00
|
|
|
{-# LANGUAGE CPP #-}
|
|
|
|
|
2013-11-23 04:54:08 +00:00
|
|
|
module Assistant.Upgrade where
|
|
|
|
|
|
|
|
import Assistant.Common
|
2013-11-23 19:50:17 +00:00
|
|
|
import Assistant.Restart
|
2013-11-23 04:54:08 +00:00
|
|
|
import qualified Annex
|
|
|
|
import Assistant.Alert
|
|
|
|
import Assistant.DaemonStatus
|
2013-11-23 16:39:36 +00:00
|
|
|
import Utility.Env
|
global webapp redirects, to finish upgrades
When an automatic upgrade completes, or when the user clicks on the upgrade
button in one webapp, but also has it open in another browser window/tab,
we have a problem: The current web server is going to stop running in
minutes, but there is no way to send a redirect to the web browser to the
new url.
To solve this, used long polling, so the webapp is always listening for
urls it should redirect to. This allows globally redirecting every open
webapp. Works great! Tested with 2 web browsers with 2 tabs each.
May be useful for other purposes later too, dunno.
The overhead is 2 http requests per page load in the webapp. Due to yesod's
speed, this does not seem to noticibly delay it. Only 1 of the requests
could possibly block the page load, the other is async.
2013-11-23 18:47:38 +00:00
|
|
|
import Types.Distribution
|
2013-11-24 01:58:39 +00:00
|
|
|
import Logs.Transfer
|
|
|
|
import Logs.Web
|
2013-11-24 03:45:49 +00:00
|
|
|
import Logs.Presence
|
|
|
|
import Logs.Location
|
2013-11-24 01:58:39 +00:00
|
|
|
import Annex.Content
|
|
|
|
import qualified Backend
|
|
|
|
import qualified Types.Backend
|
|
|
|
import qualified Types.Key
|
2013-11-24 02:12:36 +00:00
|
|
|
import Assistant.TransferQueue
|
|
|
|
import Assistant.TransferSlots
|
2013-11-24 03:45:49 +00:00
|
|
|
import Remote (remoteFromUUID)
|
|
|
|
import Config.Files
|
|
|
|
import Utility.ThreadScheduler
|
|
|
|
import Utility.Tmp
|
2013-11-24 16:49:03 +00:00
|
|
|
import Utility.UserInfo
|
2013-11-24 03:45:49 +00:00
|
|
|
import qualified Utility.Lsof as Lsof
|
2013-11-24 02:12:36 +00:00
|
|
|
|
|
|
|
import qualified Data.Map as M
|
2013-11-24 03:45:49 +00:00
|
|
|
import Data.Tuple.Utils
|
2013-11-23 04:54:08 +00:00
|
|
|
|
2013-11-23 19:50:17 +00:00
|
|
|
{- Upgrade without interaction in the webapp. -}
|
|
|
|
unattendedUpgrade :: Assistant ()
|
|
|
|
unattendedUpgrade = do
|
|
|
|
prepUpgrade
|
|
|
|
url <- runRestart
|
|
|
|
postUpgrade url
|
2013-11-23 04:54:08 +00:00
|
|
|
|
|
|
|
prepUpgrade :: Assistant ()
|
|
|
|
prepUpgrade = do
|
|
|
|
void $ addAlert upgradingAlert
|
2013-11-23 16:39:36 +00:00
|
|
|
void $ liftIO $ setEnv upgradedEnv "1" True
|
2013-11-23 19:50:17 +00:00
|
|
|
prepRestart
|
2013-11-23 04:54:08 +00:00
|
|
|
|
global webapp redirects, to finish upgrades
When an automatic upgrade completes, or when the user clicks on the upgrade
button in one webapp, but also has it open in another browser window/tab,
we have a problem: The current web server is going to stop running in
minutes, but there is no way to send a redirect to the web browser to the
new url.
To solve this, used long polling, so the webapp is always listening for
urls it should redirect to. This allows globally redirecting every open
webapp. Works great! Tested with 2 web browsers with 2 tabs each.
May be useful for other purposes later too, dunno.
The overhead is 2 http requests per page load in the webapp. Due to yesod's
speed, this does not seem to noticibly delay it. Only 1 of the requests
could possibly block the page load, the other is async.
2013-11-23 18:47:38 +00:00
|
|
|
postUpgrade :: URLString -> Assistant ()
|
2013-11-23 19:50:17 +00:00
|
|
|
postUpgrade = postRestart
|
global webapp redirects, to finish upgrades
When an automatic upgrade completes, or when the user clicks on the upgrade
button in one webapp, but also has it open in another browser window/tab,
we have a problem: The current web server is going to stop running in
minutes, but there is no way to send a redirect to the web browser to the
new url.
To solve this, used long polling, so the webapp is always listening for
urls it should redirect to. This allows globally redirecting every open
webapp. Works great! Tested with 2 web browsers with 2 tabs each.
May be useful for other purposes later too, dunno.
The overhead is 2 http requests per page load in the webapp. Due to yesod's
speed, this does not seem to noticibly delay it. Only 1 of the requests
could possibly block the page load, the other is async.
2013-11-23 18:47:38 +00:00
|
|
|
|
|
|
|
autoUpgradeEnabled :: Assistant Bool
|
|
|
|
autoUpgradeEnabled = liftAnnex $ (==) AutoUpgrade . annexAutoUpgrade <$> Annex.getGitConfig
|
|
|
|
|
|
|
|
checkSuccessfulUpgrade :: IO Bool
|
|
|
|
checkSuccessfulUpgrade = isJust <$> getEnv upgradedEnv
|
|
|
|
|
|
|
|
upgradedEnv :: String
|
|
|
|
upgradedEnv = "GIT_ANNEX_UPGRADED"
|
2013-11-24 01:58:39 +00:00
|
|
|
|
2013-11-24 02:12:36 +00:00
|
|
|
{- Start downloading the distribution key from the web.
|
2013-11-24 03:45:49 +00:00
|
|
|
- Install a hook that will be run once the download is complete,
|
|
|
|
- and finishes the upgrade. -}
|
2013-11-24 02:12:36 +00:00
|
|
|
startDistributionDownload :: GitAnnexDistribution -> Assistant ()
|
|
|
|
startDistributionDownload d = do
|
|
|
|
liftAnnex $ setUrlPresent k u
|
2013-11-24 03:45:49 +00:00
|
|
|
hook <- asIO1 $ distributionDownloadComplete d cleanup
|
|
|
|
modifyDaemonStatus_ $ \s -> s
|
|
|
|
{ transferHook = M.insert k hook (transferHook s) }
|
2013-11-24 02:12:36 +00:00
|
|
|
maybe noop (queueTransfer "upgrade" Next (Just f) t)
|
|
|
|
=<< liftAnnex (remoteFromUUID webUUID)
|
|
|
|
startTransfer t
|
|
|
|
where
|
|
|
|
k = distributionKey d
|
|
|
|
u = distributionUrl d
|
|
|
|
f = takeFileName u ++ " (for upgrade)"
|
|
|
|
t = Transfer
|
|
|
|
{ transferDirection = Download
|
|
|
|
, transferUUID = webUUID
|
|
|
|
, transferKey = k
|
|
|
|
}
|
2013-11-24 03:45:49 +00:00
|
|
|
cleanup = liftAnnex $ do
|
|
|
|
removeAnnex k
|
|
|
|
setUrlMissing k u
|
|
|
|
logStatus k InfoMissing
|
2013-11-24 02:12:36 +00:00
|
|
|
|
2013-11-24 03:45:49 +00:00
|
|
|
{- Called once the download is done.
|
|
|
|
- Passed an action that can be used to clean up the downloaded file.
|
|
|
|
-
|
|
|
|
- Fsck the key to verify the download.
|
|
|
|
-}
|
|
|
|
distributionDownloadComplete :: GitAnnexDistribution -> Assistant () -> Transfer -> Assistant ()
|
|
|
|
distributionDownloadComplete d cleanup t
|
2013-11-24 04:26:20 +00:00
|
|
|
| transferDirection t == Download = do
|
|
|
|
debug ["finished downloading git-annex distribution"]
|
2013-11-24 18:04:03 +00:00
|
|
|
maybe cleanup go =<< liftAnnex (withObjectLoc k fsckit (getM fsckit))
|
2013-11-24 16:49:03 +00:00
|
|
|
| otherwise = cleanup
|
2013-11-24 01:58:39 +00:00
|
|
|
where
|
|
|
|
k = distributionKey d
|
|
|
|
fsckit f = case Backend.maybeLookupBackendName (Types.Key.keyBackendName k) of
|
|
|
|
Nothing -> return $ Just f
|
|
|
|
Just b -> case Types.Backend.fsckKey b of
|
|
|
|
Nothing -> return $ Just f
|
|
|
|
Just a -> ifM (a k f)
|
|
|
|
( return $ Just f
|
2013-11-24 03:45:49 +00:00
|
|
|
, return Nothing
|
2013-11-24 01:58:39 +00:00
|
|
|
)
|
2013-11-24 18:04:03 +00:00
|
|
|
go f = do
|
|
|
|
ua <- asIO $ upgradeToDistribution d cleanup f
|
|
|
|
fa <- asIO1 failedupgrade
|
|
|
|
liftIO $ ua `catchNonAsync` fa
|
|
|
|
failedupgrade e = do
|
|
|
|
cleanup
|
|
|
|
void $ addAlert $ upgradeFailedAlert $ show e
|
2013-11-24 01:58:39 +00:00
|
|
|
|
2013-11-24 03:45:49 +00:00
|
|
|
{- The upgrade method varies by OS.
|
|
|
|
-
|
|
|
|
- In general, find where the distribution was installed before,
|
|
|
|
- and unpack the new distribution next to it (in a versioned directory).
|
|
|
|
- Then update the programFile to point to the new version.
|
|
|
|
-}
|
|
|
|
upgradeToDistribution :: GitAnnexDistribution -> Assistant () -> FilePath -> Assistant ()
|
|
|
|
upgradeToDistribution d cleanup f = do
|
2013-11-24 05:11:04 +00:00
|
|
|
(program, deleteold) <- unpack
|
|
|
|
changeprogram program
|
2013-11-24 03:45:49 +00:00
|
|
|
cleanup
|
|
|
|
prepUpgrade
|
|
|
|
url <- runRestart
|
|
|
|
{- At this point, the new assistant is fully running, so
|
2013-11-24 05:11:04 +00:00
|
|
|
- it's safe to delete the old version. -}
|
2013-11-24 16:49:03 +00:00
|
|
|
liftIO $ void $ tryIO deleteold
|
2013-11-24 03:45:49 +00:00
|
|
|
postUpgrade url
|
|
|
|
where
|
|
|
|
changeprogram program = liftIO $ do
|
|
|
|
unlessM (boolSystem program [Param "version"]) $
|
|
|
|
error "New git-annex program failed to run! Not using."
|
|
|
|
pf <- programFile
|
|
|
|
liftIO $ writeFile pf program
|
2013-11-24 05:11:04 +00:00
|
|
|
|
|
|
|
#ifdef darwin_HOST_OS
|
|
|
|
{- OS X uses a dmg, so mount it, and copy the contents into place. -}
|
|
|
|
unpack = do
|
|
|
|
error "TODO OSX upgrade code"
|
|
|
|
#else
|
|
|
|
{- Linux uses a tarball (so could other POSIX systems), so
|
|
|
|
- untar it (into a temp directory) and move the directory
|
|
|
|
- into place. -}
|
|
|
|
unpack = liftIO $ do
|
|
|
|
olddir <- parentDir <$> readProgramFile
|
2013-11-24 18:04:03 +00:00
|
|
|
when (null olddir) $
|
|
|
|
error $ "Cannot find old distribution bundle; not upgrading."
|
2013-11-24 16:49:03 +00:00
|
|
|
newdir <- newVersionLocation d olddir "git-annex.linux."
|
2013-11-24 05:11:04 +00:00
|
|
|
whenM (doesDirectoryExist newdir) $
|
2013-11-24 18:04:03 +00:00
|
|
|
error $ "Upgrade destination directory " ++ newdir ++ "already exists; not overwriting."
|
2013-11-24 16:49:03 +00:00
|
|
|
withTmpDirIn (parentDir newdir) "git-annex.upgrade" $ \tmpdir -> do
|
2013-11-24 05:11:04 +00:00
|
|
|
let tarball = tmpdir </> "tar"
|
|
|
|
-- Cannot rely on filename extension, and this also
|
|
|
|
-- avoids problems if tar doesn't support transparent
|
|
|
|
-- decompression.
|
|
|
|
void $ boolSystem "sh"
|
|
|
|
[ Param "-c"
|
|
|
|
, Param $ "zcat < " ++ shellEscape f ++
|
|
|
|
" > " ++ shellEscape tarball
|
|
|
|
]
|
|
|
|
tarok <- boolSystem "tar"
|
|
|
|
[ Param "xf"
|
|
|
|
, Param tarball
|
|
|
|
, Param "--directory", File tmpdir
|
|
|
|
]
|
|
|
|
unless tarok $
|
|
|
|
error $ "failed to untar " ++ f
|
|
|
|
let unpacked = tmpdir </> "git-annex.linux"
|
|
|
|
unlessM (doesDirectoryExist unpacked) $
|
|
|
|
error $ "did not find git-annex.linux in " ++ f
|
|
|
|
renameDirectory unpacked newdir
|
2013-11-24 16:49:03 +00:00
|
|
|
let deleteold = do
|
|
|
|
deleteFromManifest olddir
|
|
|
|
let origdir = parentDir olddir </> "git-annex.linux"
|
|
|
|
nukeFile origdir
|
|
|
|
createSymbolicLink newdir origdir
|
|
|
|
return (newdir </> "git-annex", deleteold)
|
2013-11-24 05:11:04 +00:00
|
|
|
#endif
|
|
|
|
|
2013-11-24 16:49:03 +00:00
|
|
|
{- Finds a place to install the new version.
|
|
|
|
- Generally, put it in the parent directory of where the old version was
|
|
|
|
- installed, and use a version number in the directory name.
|
|
|
|
- If unable to write to there, instead put it in the home directory.
|
|
|
|
-}
|
|
|
|
newVersionLocation :: GitAnnexDistribution -> FilePath -> String -> IO FilePath
|
|
|
|
newVersionLocation d olddir base = go =<< tryIO (writeFile testfile "")
|
|
|
|
where
|
|
|
|
s = base ++ distributionVersion d
|
|
|
|
topdir = parentDir olddir
|
|
|
|
newloc = topdir </> s
|
|
|
|
testfile = newloc ++ ".test"
|
|
|
|
go (Right _) = do
|
|
|
|
nukeFile testfile
|
|
|
|
return newloc
|
|
|
|
go (Left _) = do
|
|
|
|
home <- myHomeDir
|
|
|
|
return $ home </> s
|
|
|
|
|
2013-11-24 05:11:04 +00:00
|
|
|
deleteFromManifest :: FilePath -> IO ()
|
|
|
|
deleteFromManifest dir = do
|
|
|
|
fs <- map (dir </>) . lines <$> catchDefaultIO "" (readFile manifest)
|
|
|
|
mapM_ nukeFile fs
|
|
|
|
nukeFile manifest
|
2013-11-24 16:56:49 +00:00
|
|
|
removeEmptyRecursive dir
|
2013-11-24 05:11:04 +00:00
|
|
|
where
|
|
|
|
manifest = dir </> "git-annex.MANIFEST"
|
2013-11-24 16:56:49 +00:00
|
|
|
|
|
|
|
removeEmptyRecursive :: FilePath -> IO ()
|
|
|
|
removeEmptyRecursive dir = do
|
|
|
|
print ("remove", dir)
|
|
|
|
mapM_ removeEmptyRecursive =<< dirContents dir
|
|
|
|
void $ tryIO $ removeDirectory dir
|
2013-11-24 03:45:49 +00:00
|
|
|
|
|
|
|
{- This is a file that the UpgradeWatcher can watch for modifications to
|
|
|
|
- detect when git-annex has been upgraded.
|
|
|
|
-}
|
|
|
|
upgradeFlagFile :: IO (Maybe FilePath)
|
|
|
|
upgradeFlagFile = ifM usingDistribution
|
|
|
|
( Just <$> programFile
|
|
|
|
, programPath
|
|
|
|
)
|
|
|
|
|
|
|
|
{- Sanity check to see if an upgrade is complete and the program is ready
|
|
|
|
- to be run. -}
|
|
|
|
upgradeSanityCheck :: IO Bool
|
|
|
|
upgradeSanityCheck = ifM usingDistribution
|
|
|
|
( doesFileExist =<< programFile
|
|
|
|
, do
|
|
|
|
-- Ensure that the program is present, and has no writers,
|
|
|
|
-- and can be run. This should handle distribution
|
|
|
|
-- upgrades, manual upgrades, etc.
|
|
|
|
v <- programPath
|
|
|
|
case v of
|
|
|
|
Nothing -> return False
|
|
|
|
Just program -> do
|
|
|
|
untilM (doesFileExist program <&&> nowriter program) $
|
|
|
|
threadDelaySeconds (Seconds 60)
|
|
|
|
boolSystem program [Param "version"]
|
|
|
|
)
|
|
|
|
where
|
|
|
|
nowriter f = null
|
|
|
|
. filter (`elem` [Lsof.OpenReadWrite, Lsof.OpenWriteOnly])
|
|
|
|
. map snd3
|
|
|
|
<$> Lsof.query [f]
|
2013-11-24 04:26:20 +00:00
|
|
|
|
|
|
|
usingDistribution :: IO Bool
|
|
|
|
usingDistribution = isJust <$> getEnv "GIT_ANNEX_STANDLONE_ENV"
|