stackage-install (empty) → 0.1.0.0
raw patch · 7 files changed
+268/−0 lines, 7 filesdep +asyncdep +basedep +bytestringsetup-changed
Dependencies added: async, base, bytestring, directory, filepath, http-client, http-client-tls, process, stackage-install, stm
Files
- ChangeLog.md +3/−0
- LICENSE +22/−0
- README.md +54/−0
- Setup.hs +2/−0
- Stackage/Install.hs +137/−0
- app/stackage-install.hs +13/−0
- stackage-install.cabal +37/−0
+ ChangeLog.md view
@@ -0,0 +1,3 @@+# 0.1.0.0++* Initial release
+ LICENSE view
@@ -0,0 +1,22 @@+The MIT License (MIT)++Copyright (c) 2015 FP Complete++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.+
+ README.md view
@@ -0,0 +1,54 @@+# stackage-install++`stackage-install` provides a wrapper around the `cabal install` command, which+will download packages more securely. Initially, this means downloading over an+HTTPS connection from FP Complete's Amazon S3 mirror of Hackage, though more+hardening is planned for the future (see future improvements below).++To install, simply run `cabal update && cabal install stackage-install`. Usage+is intended to overlap well with `cabal install`. Whenever you would have run+`cabal install foo`, you can now run `stackage-install foo` (or `stk install+foo` with [stackage-cli](http://github.com/fpco/stackage-cli) installed), which+will perform the following steps:++1. Run `cabal install --dry-run ...` to get cabal's build plan+2. Download the relevant packages from S3, and place them in the locations that `cabal-install` expects+3. Run `cabal install ...`++## Caveats++If you have a modified `remote-repo` in your ~/.cabal/config file, this tool+will not provide proper hardening. Most users do not modify their remote-repo,+so this shouldn't be an issue most of the time.++There are some combinations of `cabal install` arguments which may not+translate well to this tool. One known issue is that passing `--dry-run` is not+supported, but others may apply as well.++This tool necessarily has to call `cabal-install` twice, once to calculate the+dependencies, and then to install them. It's theoretically possible that+`cabal-install` could come up with different build plans between the two calls,+in which case the second call may download some packages insecurely. I've+opened [cabal issue #2566](https://github.com/haskell/cabal/issues/2566) about+disabling downloading in cabal.++## Why not fix cabal?++Hopefully cabal will get fixed soon, the [discussion has already+started](https://mail.haskell.org/pipermail/cabal-devel/2015-April/010124.html).+It's unfortunately unclear how long that discussion will take, and I received a+specific request to write this tool. Since it's a small amount of code, I went+ahead with this as an interim solution.++That said, some of the future enhancements discussed below are not planned for+cabal, in which case this tool will continue to remain relevant for people+looking for additional security beyond transport security.++## Why Stackage?++See [the same question and its answer on stackage-update](https://github.com/fpco/stackage-update#why-stackage).++## Future enhancements++* Check hashes of all packages downloaded against a collection of package hashes+* Verify signatures from authors against the [signature archive](https://github.com/commercialhaskell/sig-archive)
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ Stackage/Install.hs view
@@ -0,0 +1,137 @@+{-# LANGUAGE ViewPatterns #-}+-- | Functionality for downloading packages securely for cabal's usage.+module Stackage.Install+ ( install+ , download+ , Settings+ , defaultSettings+ ) where++import Control.Applicative ((*>))+import Control.Concurrent.Async (Concurrently (..))+import Control.Concurrent.STM (atomically, newTVarIO, readTVar,+ writeTVar)+import Control.Monad (join, unless)+import qualified Data.ByteString as S+import qualified Data.ByteString.Char8 as S8+import qualified Data.Foldable as F+import Data.Function (fix)+import Data.List (isPrefixOf)+import Network.HTTP.Client (Manager, brRead, newManager,+ parseUrl, responseBody, withResponse)+import Network.HTTP.Client.TLS (tlsManagerSettings)+import System.Directory (createDirectoryIfMissing,+ doesFileExist,+ getAppUserDataDirectory, renameFile)+import System.Exit (ExitCode)+import System.FilePath (takeDirectory, (<.>), (</>))+import System.IO (IOMode (WriteMode), stdout,+ withBinaryFile)+import System.Process (rawSystem, readProcess)++-- | Run cabal install with --dry-run, determine necessary dependencies,+-- download them, and rerun cabal install without --dry-run.+--+-- Since 0.1.0.0+install :: Settings -> [String] -> IO ExitCode+install s args = do+ out <- readProcess (_cabalCommand s) ("install":"--dry-run":args) ""+ let pkgs = map toPair $ filter (not . toIgnore) $ lines out+ download s pkgs+ rawSystem (_cabalCommand s) ("install":args)+ where+ toIgnore str = ' ' `elem` str || '-' `notElem` str++ toPair :: String -> (String, String)+ toPair orig =+ (pkg, ver)+ where+ (ver', pkg') = break (== '-') $ reverse orig+ ver = reverse ver'+ pkg = reverse $ drop 1 pkg'++-- | Settings used by 'download' and 'install'.+--+-- Since 0.1.0.0+data Settings = Settings+ { _getManager :: !(IO Manager)+ , _cabalCommand :: !FilePath+ , _downloadPrefix :: !String+ , _onDownload :: !(String -> IO ())+ , _connections :: !Int+ }++-- | Default value for 'Settings'.+--+-- Since 0.1.0.0+defaultSettings :: Settings+defaultSettings = Settings+ { _getManager = newManager tlsManagerSettings+ , _cabalCommand = "cabal"+ , _downloadPrefix = "https://s3.amazonaws.com/hackage.fpcomplete.com/package/"+ , _onDownload = \s -> S8.hPut stdout $ S8.pack $ concat+ [ "Downloading "+ , s+ , "\n"+ ]+ , _connections = 8+ }++-- | Download the given name,version pairs into the directory expected by cabal.+--+-- Since 0.1.0.0+download :: F.Foldable f => Settings -> f (String, String) -> IO ()+download s pkgs = do+ man <- _getManager s+ cabalDir <- getAppUserDataDirectory "cabal"+ parMapM_ (_connections s) (go cabalDir man) pkgs+ where+ unlessM p f = do+ p' <- p+ unless p' f++ go cabalDir man (name, version) = do+ unlessM (doesFileExist fp) $ do+ _onDownload s pkg+ createDirectoryIfMissing True $ takeDirectory fp+ req <- parseUrl url+ withResponse req man $ \res -> do+ let tmp = fp <.> "tmp"+ withBinaryFile tmp WriteMode $ \h -> fix $ \loop -> do+ bs <- brRead $ responseBody res+ unless (S.null bs) $ do+ S.hPut h bs+ loop+ renameFile tmp fp+ where+ pkg = concat [name, "-", version]+ targz = pkg ++ ".tar.gz"+ url = _downloadPrefix s ++ targz+ fp = cabalDir </>+ "packages" </>+ "hackage.haskell.org" </>+ name </>+ version </>+ targz++parMapM_ :: F.Foldable f+ => Int+ -> (a -> IO ())+ -> f a+ -> IO ()+parMapM_ (max 1 -> 1) f xs = F.mapM_ f xs+parMapM_ cnt f xs0 = do+ var <- newTVarIO $ F.toList xs0+ let worker :: IO ()+ worker = fix $ \loop -> join $ atomically $ do+ xs <- readTVar var+ case xs of+ [] -> return $ return ()+ x:xs' -> do+ writeTVar var xs'+ return $ do+ f x+ loop+ workers 1 = Concurrently worker+ workers i = Concurrently worker *> workers (i - 1)+ runConcurrently $ workers cnt
+ app/stackage-install.hs view
@@ -0,0 +1,13 @@+import Control.Monad (when)+import Stackage.Install+import System.Environment (getArgs)+import System.Exit (exitWith)++main :: IO ()+main = do+ args <- getArgs+ when ("--dry-run" `elem` args)+ $ error "You can't call this command with --dry-run as an argument"+ if args == ["--summary"]+ then putStrLn "Secure download wrapper around cabal install"+ else install defaultSettings args >>= exitWith
+ stackage-install.cabal view
@@ -0,0 +1,37 @@+name: stackage-install+version: 0.1.0.0+synopsis: Secure download of packages for cabal-install+description: For more information, see <https://www.stackage.org/package/stackage-install>+homepage: https://github.com/fpco/stackage-install+license: MIT+license-file: LICENSE+author: Michael Snoyman+maintainer: michael@snoyman.com+category: Distribution+build-type: Simple+extra-source-files: ChangeLog.md, README.md+cabal-version: >=1.10++library+ exposed-modules: Stackage.Install+ build-depends: base >= 4.5 && < 5+ , bytestring >= 0.9+ , http-client >= 0.4+ , http-client-tls >= 0.2+ , directory >= 1.1+ , filepath >= 1.2+ , process >= 1+ , async+ , stm+ default-language: Haskell2010++executable stackage-install+ main-is: stackage-install.hs+ hs-source-dirs: app+ build-depends: base+ , stackage-install+ default-language: Haskell2010++source-repository head+ type: git+ location: git://github.com/fpco/stackage-install.git