diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,3 @@
+# 0.1.0.0
+
+* Initial release
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -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.
+
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -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)
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/Stackage/Install.hs b/Stackage/Install.hs
new file mode 100644
--- /dev/null
+++ b/Stackage/Install.hs
@@ -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
diff --git a/app/stackage-install.hs b/app/stackage-install.hs
new file mode 100644
--- /dev/null
+++ b/app/stackage-install.hs
@@ -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
diff --git a/stackage-install.cabal b/stackage-install.cabal
new file mode 100644
--- /dev/null
+++ b/stackage-install.cabal
@@ -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
