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,20 @@
+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,34 @@
+## stackage-update
+
+This package provides an executable, `stackage-update`, which provides the same
+functionality as `cabal update` (it updates your local package index). However,
+instead of downloading the entire package index as a compressed tarball over
+insecure HTTP, it uses `git` to incrementally update your package list, and
+downloads over secure HTTPS.
+
+It has minimal Haskell library dependencies (all dependencies are shipped with
+GHC itself) and only requires that the `git` executable be available on the
+PATH. It builds on top of the
+[all-cabal-files](https://github.com/commercialhaskell/all-cabal-files)
+repository.
+
+### Usage
+
+Install from Hackage as usual with:
+
+```
+cabal update
+cabal install stackage-update
+```
+
+From then on, simply run `stackage-update` instead of `cabal update`.
+
+### Why stackage?
+
+You may be wondering why this tool is called `stackage-update`, when in fact
+the functionality is useful outside of [the Stackage
+project](https://www.stackage.org/) itself. The reason is that the naming
+allows it to play nicely with the other Stackage command line tooling.
+Concretely, that means that if you have stackage-cli installed, stackage-update
+works as a plugin. However, you can certainly use `stackage-update` on its own
+without any other tooling or dependencies on the Stackage project.
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/Update.hs b/Stackage/Update.hs
new file mode 100644
--- /dev/null
+++ b/Stackage/Update.hs
@@ -0,0 +1,85 @@
+module Stackage.Update
+    ( stackageUpdate
+    , StackageUpdateSettings
+    , defaultStackageUpdateSettings
+    ) where
+
+import           Control.Exception      (IOException, try)
+import           Control.Monad          (when)
+import           System.Directory       (createDirectoryIfMissing,
+                                         doesDirectoryExist, findExecutable,
+                                         getAppUserDataDirectory, removeFile)
+import           System.Exit            (ExitCode (ExitSuccess), exitWith)
+import           System.FilePath        ((<.>), (</>))
+import           System.Process         (createProcess, cwd, proc,
+                                         waitForProcess)
+import System.IO (hPutStrLn, stderr)
+
+-- | Settings for controlling the update process.
+--
+-- Use 'defaultStackageUpdateSettings' to create a value of this type.
+--
+-- Since 0.1.0.0
+data StackageUpdateSettings = StackageUpdateSettings
+
+-- | Default settings for the update process.
+--
+-- Since 0.1.0.0
+defaultStackageUpdateSettings :: StackageUpdateSettings
+defaultStackageUpdateSettings = StackageUpdateSettings
+
+-- | Perform an update from the Git repository
+stackageUpdate :: StackageUpdateSettings -> IO ()
+stackageUpdate StackageUpdateSettings = do
+    mgit <- findExecutable "git"
+    git <-
+        case mgit of
+            Just git -> return git
+            Nothing -> error "Please install git and provide the executable on your PATH"
+
+    suDir <- getAppUserDataDirectory "stackage-update"
+    let acfDir = suDir </> "all-cabal-files"
+    repoExists <- doesDirectoryExist acfDir
+    if repoExists
+        then runIn suDir acfDir "git" ["fetch"]
+        else runIn suDir suDir "git" ["clone", "https://github.com/commercialhaskell/all-cabal-files.git", "-b", "hackage"]
+
+    cabalDir <- getAppUserDataDirectory "cabal"
+    let hackageDir = cabalDir </> "packages" </> "hackage.haskell.org"
+    createDirectoryIfMissing True hackageDir
+
+    let tarFile = hackageDir </> "00-index.tar"
+        gzFile = tarFile <.> "gz"
+
+    _ <- tryIO $ removeFile tarFile
+    runIn suDir acfDir "git" ["archive", "--format=tar", "-o", tarFile, "origin/hackage"]
+
+tryIO :: IO a -> IO (Either IOException a)
+tryIO = try
+
+runIn :: FilePath -- ^ su directory
+      -> FilePath -- ^ directory
+      -> FilePath -- ^ command
+      -> [String] -- ^ command line arguments
+      -> IO ()
+runIn suDir dir cmd args = do
+    createDirectoryIfMissing True dir
+    (Nothing, Nothing, Nothing, ph) <- createProcess (proc cmd args)
+        { cwd = Just dir
+        }
+    ec <- waitForProcess ph
+    when (ec /= ExitSuccess) $ do
+        hPutStrLn stderr $ concat
+            [ "Exit code "
+            , show ec
+            , " while running "
+            , show (cmd:args)
+            , " in "
+            , dir
+            ]
+        hPutStrLn stderr $ concat
+            [ "If the problem persists, please delete the following directory "
+            , "and try again"
+            ]
+        hPutStrLn stderr suDir
+        exitWith ec
diff --git a/app/stackage-update.hs b/app/stackage-update.hs
new file mode 100644
--- /dev/null
+++ b/app/stackage-update.hs
@@ -0,0 +1,10 @@
+import Stackage.Update
+import System.Environment (getArgs)
+
+main :: IO ()
+main = do
+    args <- getArgs
+    case args of
+        ["--help"] -> putStrLn "Run this command with no arguments to update your package index"
+        ["--summary"] -> putStrLn "Update your package index incrementally (requires git)"
+        _ -> stackageUpdate defaultStackageUpdateSettings
diff --git a/stackage-update.cabal b/stackage-update.cabal
new file mode 100644
--- /dev/null
+++ b/stackage-update.cabal
@@ -0,0 +1,31 @@
+name:                stackage-update
+version:             0.1.0.0
+synopsis:            Update your package index incrementally (requires git)
+homepage:            https://github.com/fpco/stackage-update
+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.Update
+  default-language:    Haskell2010
+  build-depends:       base       >= 4.5 && < 5
+                     , directory  >= 1.1
+                     , filepath   >= 1.2
+                     , process    >= 1.1
+
+executable stackage-update
+  main-is:             stackage-update.hs
+  hs-source-dirs:      app
+  build-depends:       base, stackage-update
+  default-language:    Haskell2010
+
+source-repository head
+  type:     git
+  location: git://github.com/fpco/stackage-update.git
