diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,5 @@
+# Revision history for stack-all
+
+## 0.1.0 -- 2020-11-14
+- initial release with --create-config, --debug, --cmd, --newest
+- VersionSpec: --all-lts, --oldest, and lts args
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2020, Jens Petersen
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Jens Petersen nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Main.hs b/Main.hs
new file mode 100644
--- /dev/null
+++ b/Main.hs
@@ -0,0 +1,141 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+import Control.Monad.Extra
+import Data.Ini.Config
+import Data.List.Extra
+import qualified Data.Text.IO as T
+import SimpleCmd
+import SimpleCmdArgs
+import System.Directory
+import System.Exit
+import System.FilePath
+import System.IO
+import System.Process
+
+import Paths_stack_all (version)
+
+-- FIXME allow specific snapshots?
+-- FIXME lts latest
+data Snapshot = LTS Int | Nightly
+  deriving (Eq, Ord)
+
+readSnap :: String -> Snapshot
+readSnap "nightly" = Nightly
+readSnap snap =
+  if "lts" `isPrefixOf` snap then
+    let major = read (dropPrefix "lts-" snap) in LTS major
+  else error' $ "malformed snapshot " ++ snap
+
+showSnap :: Snapshot -> String
+showSnap Nightly = "nightly"
+showSnap (LTS ver) = "lts-" ++ show ver
+
+defaultOldest :: Snapshot
+defaultOldest = LTS 11
+
+allSnaps :: [Snapshot]
+allSnaps = [Nightly, LTS 16, LTS 14, LTS 13, LTS 12, LTS 11,
+            LTS 10, LTS 9, LTS 8, LTS 6, LTS 5, LTS 4, LTS 2, LTS 1]
+
+data VersionSpec = DefaultVersions | Oldest Snapshot | AllVersions | VersionList [Snapshot]
+
+main :: IO ()
+main = do
+  hSetBuffering stdout NoBuffering
+  unlessM (doesFileExist "stack.yaml") $
+    error' "no stack.yaml found"
+  simpleCmdArgs (Just version) "Build over Stackage versions"
+    "stack-all builds projects easily across different Stackage versions" $
+    main' <$>
+    switchWith 'c' "create-config" "Create a project .stack-all file" <*>
+    switchWith 'd' "debug" "Verbose stack build output on error" <*>
+    optional (readSnap <$> strOptionWith 'n' "newest" "lts-MAJOR" "Newest LTS release to build from") <*>
+    optional (strOptionWith 'C' "cmd" "COMMAND" "Specify a stack command [default: build]") <*>
+    versionSpec
+  where
+    versionSpec =
+      Oldest . readSnap <$> strOptionWith 'o' "oldest" "lts-MAJOR" "Oldest compatible LTS release" <|>
+      VersionList . map readSnap <$> some (strArg "LTS") <|>
+      flagWith DefaultVersions AllVersions 'a' "all-lts" "Try to build back to LTS 1 even"
+
+main' :: Bool -> Bool -> Maybe Snapshot -> Maybe String -> VersionSpec -> IO ()
+main' createConfig debug mnewest mcmd versionSpec = do
+  if createConfig then
+    case versionSpec of
+      Oldest oldest -> createStackAll oldest
+      _ -> error' "creating .stack-all requires --oldest LTS"
+    else do
+    versions <-
+      case versionSpec of
+        DefaultVersions -> do
+          oldest <- fromMaybeM (return defaultOldest) getOldestLTS
+          return $ filter (>= oldest) allSnaps
+        AllVersions -> return allSnaps
+        Oldest ver -> return $ filter (>= ver) allSnaps
+        VersionList vers -> return vers
+    configs <- filter isStackConf <$> listDirectory "."
+    let newestFilter = maybe id (filter . (>=)) mnewest
+    mapM_ (stackBuild configs debug mcmd) (newestFilter versions)
+  where
+    isStackConf :: FilePath -> Bool
+    isStackConf f = "stack-" `isPrefixOf` f && "yaml" `isExtensionOf` f
+
+stackAllFile :: FilePath
+stackAllFile = ".stack-all"
+
+createStackAll :: Snapshot -> IO ()
+createStackAll snap = do
+  exists <- doesFileExist stackAllFile
+  if exists then error' $ stackAllFile ++ " already exists"
+    else do
+    writeFile stackAllFile $
+      "[versions]\n# reason comment\noldest = " ++ showSnap snap ++ "\n"
+
+getOldestLTS :: IO (Maybe Snapshot)
+getOldestLTS = do
+  haveConfig <- doesFileExist stackAllFile
+  if haveConfig then
+    Just . readSnap <$> readIniConfig stackAllFile rcParser id
+    else return Nothing
+  where
+    rcParser :: IniParser String
+    rcParser =
+      section "versions" $
+      fieldOf "oldest" string
+
+    readIniConfig :: FilePath -> IniParser a -> (a -> b) -> IO b
+    readIniConfig inifile iniparser fn = do
+      ini <- T.readFile inifile
+      return $ either error fn $ parseIniFile ini iniparser
+
+stackBuild :: [FilePath] -> Bool -> Maybe String -> Snapshot -> IO ()
+stackBuild configs debug mcmd snap = do
+  let command = maybe ["build"] words mcmd
+      config =
+        case sort (filter (snapConfig <=) configs) of
+          [] -> []
+          (cfg:_) -> ["--stack-yaml", cfg]
+      args = ["-v" | debug] ++ ["--resolver", showSnap snap] ++
+             config ++ command
+  if debug
+    then debugBuild args
+    else cmd_ "stack" args
+  putStrLn ""
+  where
+    snapConfig :: FilePath
+    snapConfig = "stack-" ++ compactSnap snap <.> "yaml"
+      where
+        compactSnap :: Snapshot -> String
+        compactSnap Nightly = "nightly"
+        compactSnap (LTS ver) = "lts" ++ show ver
+
+    debugBuild :: [String] -> IO ()
+    debugBuild args = do
+      putStr $ "stack " ++ unwords args
+      (ret,out,err) <- readProcessWithExitCode "stack" args ""
+      putStrLn "\n"
+      unless (null out) $ putStrLn out
+      unless (ret == ExitSuccess) $ do
+        -- stack verbose includes info line with all stackages (> 500kbytes)
+        mapM_ putStrLn $ filter ((<10000) . length) . lines $ err
+        error' $ showSnap snap ++ " build failed"
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,28 @@
+# stack-all
+
+A CLI tool for building Haskell projects easily over Stackage major versions.
+
+This is how I do my Haskell "build ci" now locally.
+
+## Usage
+
+`stack-all` runs `stack build` over recent Stackage LTS major versions
+and Nightly: by default currently: nightly, lts-16, ..., lts-11.
+
+Note that stack-all will automatically use `stack-ltsXX.yaml`, even for older lts releases: eg say you have `stack-lts13.yaml` in your project, then it will also be used for building lts-12 (unless you have a `stack-lts12.yaml` config file of course).
+
+You can specify the oldest working LTS for a project with `stack-all -o lts-13` or set it in a `.stack-all` file containing:
+```
+[versions]
+# lts-12 foo-bar too old
+oldest = lts-13
+```
+which can be created with `stack-all -c -o lts-13`.
+
+Happy stack building!
+
+## Install
+Run `stack install` or `cabal install` in the source.
+
+## Contribute
+See https://github.com/juhp/stack-all
diff --git a/stack-all.cabal b/stack-all.cabal
new file mode 100644
--- /dev/null
+++ b/stack-all.cabal
@@ -0,0 +1,48 @@
+name:                stack-all
+version:             0.1
+synopsis:            CLI tool for building across Stackage major versions
+description:
+        Build your Haskell project over Stackage major versions.
+license:             BSD3
+license-file:        LICENSE
+author:              Jens Petersen <juhpetersen@gmail.com>
+maintainer:          Jens Petersen <juhpetersen@gmail.com>
+copyright:           2020  Jens Petersen <juhpetersen@gmail.com>
+category:            Distribution
+homepage:            https://github.com/juhp/stack-all
+bug-reports:         https://github.com/juhp/stack-all/issues
+build-type:          Simple
+extra-doc-files:     README.md
+                     ChangeLog.md
+cabal-version:       1.18
+tested-with:         GHC == 8.4.4, GHC == 8.6.5,  GHC == 8.8.4, GHC == 8.10.2
+
+source-repository head
+  type:                git
+  location:            https://github.com/juhp/stack-all.git
+
+executable stack-all
+  main-is:             Main.hs
+  other-modules:       Paths_stack_all
+  build-depends:       base < 5,
+                       config-ini,
+                       directory,
+                       extra >= 1.6.15,
+                       filepath,
+                       process,
+                       simple-cmd >= 0.1.4,
+                       simple-cmd-args,
+                       text
+  default-language:    Haskell2010
+  ghc-options:         -Wall
+  if impl(ghc >= 8.0)
+    ghc-options:       -Wcompat
+                       -Widentities
+                       -Wincomplete-uni-patterns
+                       -Wincomplete-record-updates
+                       -Wredundant-constraints
+  if impl(ghc >= 8.2)
+    ghc-options:       -fhide-source-paths
+  if impl(ghc >= 8.4)
+    ghc-options:       -Wmissing-export-lists
+                       -Wpartial-fields
