packages feed

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 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