diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2008-2015 Dino Morelli
+Copyright (c) 2008-2016 Dino Morelli
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -67,26 +67,32 @@
 epubzip is a handy utility for zipping up the files that comprise an epub into an .epub zip file. Using the same technology as epubname, it can try to make a meaningful filename for the book.
 
 
-## Getting source
+## Getting this software
 
-- Download the cabalized source package [from Hackage](http://hackage.haskell.org/package/epub-tools)
+Binaries
+
 - epub-tools is available for Arch Linux [from the AUR](http://aur.archlinux.org/packages/epub-tools/)
-- Download [binaries for Windows](http://ui3.info/d/proj/epub-tools/epub-tools-2.7-win.zip)
-- Get the source with darcs: `$ darcs get http://hub.darcs.net/dino/epub-tools`
-- If you're just looking, [browse the source](http://hub.darcs.net/dino/epub-tools)
+- Download [binaries for Windows](http://ui3.info/d/proj/epub-tools/epub-tools-2.9-win.zip)
 
-And once you have it, building the usual way:
 
-      $ cabal configure --enable-tests
-      $ cabal build
-      $ cabal test
-      $ cabal install
+Building and installing from source with cabal-install:
 
+      $ cabal update ; cabal install epub-tools
 
-## Installing
 
-Build and install with cabal-install:
-  `$ cabal update ; cabal install epub-tools`
+Getting source for development
+
+- Download the cabalized source package [from Hackage](http://hackage.haskell.org/package/epub-tools)
+- Get the source with darcs: `$ darcs clone http://hub.darcs.net/dino/epub-tools`
+- Get the source with stack: `$ stack unpack epub-tools`
+- If you're just looking, [browse the source](http://hub.darcs.net/dino/epub-tools)
+
+
+Once you have source, building the usual way:
+
+      $ stack build
+      $ stack test
+      $ stack haddock
 
 
 ## Contact
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,12 @@
+2.9 (2016-10-19)
+
+   * Switched build to stack
+   * Added hsinstall installation script and updated windows dist script
+   * Various cabal file updates
+   * Moved copyright date up to 2016
+   * Updated README with better instructions for getting this software
+
+
 2.8 (2015-09-18)
 
    * Fixed some magazine and anthology naming rules
diff --git a/epub-tools.cabal b/epub-tools.cabal
--- a/epub-tools.cabal
+++ b/epub-tools.cabal
@@ -1,28 +1,29 @@
 name:                epub-tools
-cabal-version:       >= 1.8
-version:             2.8
+cabal-version:       >= 1.10
+version:             2.9
 build-type:          Simple
 license:             BSD3
 license-file:        LICENSE
-copyright:           2008-2015 Dino Morelli
+copyright:           2008-2016 Dino Morelli
 author:              Dino Morelli 
 maintainer:          Dino Morelli <dino@ui3.info>
-stability:           stable
 homepage:            http://hub.darcs.net/dino/epub-tools
 synopsis:            Command line utilities for working with epub files
 description:         A suite of command-line utilities for creating and manipulating epub book files. Included are: epubmeta, epubname, epubzip
 category:            Application, Console
-tested-with:         GHC >= 7.10.1
+tested-with:         GHC >= 8.0.1
 
 extra-source-files:  changelog.md
                      doc/dev/notes.md
                      doc/hcar/epubtoolsCommandlineepubU-De.tex
                      doc/INSTALL
                      README.md
+                     stack.yaml
                      testsuite/EpubTools/*.hs
                      testsuite/EpubTools/Test/EpubName/*.hs
                      TODO.md
                      util/all-books.hs
+                     util/install.hs
                      util/prefs/boring
                      util/win-dist.sh
 
@@ -43,6 +44,7 @@
                      EpubTools.EpubMeta.Opts
                      EpubTools.EpubMeta.Util
    ghc-options:      -Wall -rtsopts -with-rtsopts=-K32m
+   default-language: Haskell2010
 
 executable           epubname
    main-is:          EpubTools/epubname.hs
@@ -61,6 +63,7 @@
                      EpubTools.EpubName.Prompt
                      EpubTools.EpubName.Util
    ghc-options:      -Wall -rtsopts -with-rtsopts=-K32m
+   default-language: Haskell2010
 
 test-suite           epubname-tests
    type:             exitcode-stdio-1.0
@@ -68,12 +71,34 @@
    build-depends:    base, directory, epub-metadata >= 4.0, filepath,
                      HUnit, mtl, parsec, regex-compat
    hs-source-dirs:   src testsuite
+   other-modules:    EpubTools.EpubName.Doc.Rules
+                     EpubTools.EpubName.Format.Author
+                     EpubTools.EpubName.Format.Compile
+                     EpubTools.EpubName.Format.Format
+                     EpubTools.EpubName.Format.PubYear
+                     EpubTools.EpubName.Format.Util
+                     EpubTools.EpubName.Main
+                     EpubTools.EpubName.Opts
+                     EpubTools.EpubName.Util
+                     EpubTools.Test.EpubName.Format
+                     EpubTools.Test.EpubName.PubYear
    ghc-options:      -Wall -rtsopts -with-rtsopts=-K32m
+   default-language: Haskell2010
 
 executable           epubzip
    main-is:          EpubTools/epubzip.hs
    build-depends:    base >= 3 && < 5, directory, epub-metadata >= 4.0,
                      filepath, mtl, parsec, regex-compat
    hs-source-dirs:   src
-   other-modules:    EpubTools.EpubZip.Opts
+   other-modules:    EpubTools.EpubName.Doc.Rules
+                     EpubTools.EpubName.Format.Author
+                     EpubTools.EpubName.Format.Compile
+                     EpubTools.EpubName.Format.Format
+                     EpubTools.EpubName.Format.PubYear
+                     EpubTools.EpubName.Format.Util
+                     EpubTools.EpubName.Main
+                     EpubTools.EpubName.Opts
+                     EpubTools.EpubName.Util
+                     EpubTools.EpubZip.Opts
    ghc-options:      -Wall
+   default-language: Haskell2010
diff --git a/src/EpubTools/epubname.hs b/src/EpubTools/epubname.hs
--- a/src/EpubTools/epubname.hs
+++ b/src/EpubTools/epubname.hs
@@ -45,7 +45,7 @@
 
 {- Format and display output for a book that was processed
 -}
-displayResults :: (MonadError String m, MonadIO m) => Options
+displayResults :: MonadIO m => Options
    -> FilePath -> FilePath -> String -> Package -> Metadata -> m ()
 displayResults opts oldPath newPath fmtUsed pkg md =
    liftIO $ printf "%s -> %s%s\n" oldPath newPath
diff --git a/stack.yaml b/stack.yaml
new file mode 100644
--- /dev/null
+++ b/stack.yaml
@@ -0,0 +1,4 @@
+resolver: nightly-2016-10-18
+
+packages:
+- .
diff --git a/util/install.hs b/util/install.hs
new file mode 100644
--- /dev/null
+++ b/util/install.hs
@@ -0,0 +1,329 @@
+#! /usr/bin/env stack
+{- stack runghc -}
+
+{-# LANGUAGE ScopedTypeVariables #-}
+
+import Control.Exception
+import Control.Monad
+import Data.List
+import Data.Version
+import Distribution.Package
+import Distribution.PackageDescription hiding ( error, options )
+import Distribution.PackageDescription.Parse
+import Distribution.Verbosity
+import Distribution.Version
+import System.Console.GetOpt
+import System.Directory
+import System.Environment
+import System.Exit
+import System.FilePath
+import System.Process
+import Text.Printf
+import Text.Read
+
+
+defaultOptions :: Options
+defaultOptions = Options
+   { optClean = False
+   , optDelete = False
+   , optHelp = False
+   , optLink = False
+   , optPrefix = "/opt"
+   , optRsrcCpVerbose = True
+   , optInstType = FHS
+   , optVersion = True
+   }
+
+data InstallType = Bundle | FHS deriving Eq
+
+
+main :: IO ()
+main = do
+   -- Parse args
+   (opts, _) <- parseOpts =<< getArgs
+
+   -- User asked for help
+   when (optHelp opts) $ putStrLn usageText >> exitSuccess
+
+   -- Locate cabal file
+   cabalFiles <- (filter $ isSuffixOf ".cabal") <$> getDirectoryContents "."
+
+   when (null cabalFiles) $ do
+      die "Can't continue because no cabal files were found in ."
+
+   -- Parse the cabal file and extract things we need from it
+   -- then pass a pile of what we know to a function to create the
+   -- installation dirs
+   dirs <- constructDirs opts . package . packageDescription
+      <$> readPackageDescription normal (head cabalFiles)
+
+
+   -- Perform the installation
+
+   -- Remove existing install directory
+   appDirExists <- doesDirectoryExist $ appDir dirs
+   when (optDelete opts && appDirExists) $ do
+      putStrLn $ "Removing existing directory " ++ (appDir dirs)
+      removeDirectoryRecursive $ appDir dirs
+
+   -- Clean before building
+   when (optClean opts) $ system "stack clean" >> return ()
+
+   -- Copy the binaries
+   createDirectoryIfMissing True $ binDir dirs
+   installExitCode <- system $ "stack install --local-bin-path=" ++ (binDir dirs)
+   unless (ok installExitCode) $ die "Can't continue because stack install failed"
+
+   -- Copy additional scripts
+   {-
+   putStrLn "Copying additional scripts"
+   mapM_ (\f -> copyFile ("util" </> f) (binDir dirs </> f))
+      [ "script1.sh", "script2.hs" ]
+   -}
+
+   -- Copy the license
+   putStrLn "\nCopying LICENSE"
+   createDirectoryIfMissing True $ docDir dirs
+   copyFile "LICENSE" (docDir dirs </> "LICENSE")
+
+   -- Copy the resources
+   let rsrcDirSrc = "." </> "resources"
+   rsrcsExist <- doesDirectoryExist rsrcDirSrc
+   when rsrcsExist $ do
+      putStrLn $ "\nCopying resources"
+      copyTree (optRsrcCpVerbose opts) rsrcDirSrc (rsrcDir dirs)
+      return ()
+
+   -- Make the symlink
+   when (optLink opts) $ do
+      if (optInstType opts == FHS) then
+         putStrLn "No link will be made because installation type is fhs"
+      else if (not . optVersion $ opts) then
+         putStrLn "No link will be made because the app dir already has no version part"
+      else do
+         printf "Making symbolic link now %s -> %s\n" (linkPath dirs) (appDir dirs)
+         system $ printf "rm %s" (linkPath dirs)
+         system $ printf "ln -s %s %s" (appDir dirs) (linkPath dirs)
+         return ()
+
+   exitSuccess
+
+
+data Dirs = Dirs
+   { appDir :: FilePath
+   , linkPath :: FilePath
+   , binDir :: FilePath
+   , docDir :: FilePath
+   , rsrcDir :: FilePath
+   }
+
+
+constructDirs :: Options -> PackageId -> Dirs
+constructDirs opts pkgId =
+   Dirs appDir' linkPath' binDir' (appDir' </> "doc") (appDir' </> "resources")
+
+   where
+      project = unPackageName . pkgName $ pkgId
+      version = showVersion . pkgVersion $ pkgId
+      versionPart = if optVersion opts then "-" ++ version else ""
+      appDir' = case (optInstType opts) of
+         Bundle -> optPrefix opts </> (project ++ versionPart)
+         FHS    -> optPrefix opts </> "share" </> (project ++ versionPart)
+      linkPath' = optPrefix opts </> project
+      binDir' = case (optInstType opts) of
+         Bundle -> appDir' </> "bin"
+         FHS    -> optPrefix opts </> "bin"
+
+
+{- Turn an exit code (say, from system) into a Bool
+-}
+ok :: ExitCode -> Bool
+ok ExitSuccess = True
+ok _           = False
+
+
+{-
+   Argument parsing code
+-}
+
+data Options = Options
+   { optClean :: Bool
+   , optDelete :: Bool
+   , optHelp :: Bool
+   , optLink :: Bool
+   , optPrefix :: FilePath
+   , optRsrcCpVerbose :: Bool
+   , optInstType :: InstallType
+   , optVersion :: Bool
+   }
+
+
+instance Read InstallType where
+   readsPrec _ "bundle" = [(Bundle, "")]
+   readsPrec _ "fhs"    = [(FHS, "")]
+   readsPrec _ _        = []
+
+instance Show InstallType where
+   show Bundle = "bundle"
+   show FHS = "fhs"
+
+
+readInstallType :: String -> InstallType
+readInstallType s =
+   case (readEither s) of
+      Left _ -> error $ printf "Can't continue because %s is not a valid install type\n\n%s" s usageText
+      Right t -> t
+
+
+options :: [OptDescr (Options -> Options)]
+options =
+   [ Option ['c'] ["clean"]
+      (NoArg (\opts -> opts { optClean = True } ))
+      ("Do 'stack clean' first." ++ (defaultText . optClean $ defaultOptions))
+   , Option ['C'] ["no-clean"]
+      (NoArg (\opts -> opts { optClean = False } ))
+      ("Do not 'stack clean' first."
+         ++ (defaultText . not . optClean $ defaultOptions))
+   , Option ['d'] ["delete"]
+      (NoArg (\opts -> opts { optDelete = True } ))
+      ("Delete the app directory before copying files."
+         ++ (defaultText . optDelete $ defaultOptions))
+   , Option ['D'] ["no-delete"]
+      (NoArg (\opts -> opts { optDelete = False } ))
+      ("Do not delete the app directory before copying files."
+         ++ (defaultText . not . optDelete $ defaultOptions))
+   , Option ['h'] ["help"]
+      (NoArg (\opts -> opts { optHelp = True } ))
+      "This help information."
+   , Option ['l'] ["link"]
+      (NoArg (\opts -> opts { optLink = True } ))
+      ("Create symlink PROJECT -> PROJECT-VERSION in PREFIX dir. Only useful for bundle installations. Does not work on Windows."
+         ++ (defaultText . optLink $ defaultOptions))
+   , Option ['L'] ["no-link"]
+      (NoArg (\opts -> opts { optLink = True } ))
+      ("Do not create symlink PROJECT -> PROJECT-VERSION in PREFIX dir."
+         ++ (defaultText . not . optLink $ defaultOptions))
+   , Option ['p'] ["prefix"]
+      (ReqArg (\s opts -> opts { optPrefix = s } ) "PREFIX" )
+      (printf "Install prefix directory. Defaults to %s so what you'll end up with is %s/PROJECT-VERSION"
+         (optPrefix defaultOptions) (optPrefix defaultOptions))
+   , Option ['r'] ["resource-copy-verbose"]
+      (NoArg (\opts -> opts { optRsrcCpVerbose = True } ))
+      ("Be chatty when copying the resources directory."
+         ++ (defaultText . optRsrcCpVerbose $ defaultOptions))
+   , Option ['R'] ["no-resource-copy-verbose"]
+      (NoArg (\opts -> opts { optRsrcCpVerbose = False } ))
+      ("Don't be chatty when copying the resources directory. Useful when there are a LOT of resources."
+         ++ (defaultText . not . optRsrcCpVerbose $ defaultOptions))
+   , Option ['t'] ["type"]
+      (ReqArg (\s opts -> opts { optInstType = readInstallType s } ) "INST_TYPE" )
+      (printf "Installation type, see INSTALLATION TYPE below for details. Default: %s"
+         (show . optInstType $ defaultOptions))
+   , Option ['v'] ["version"]
+      (NoArg (\opts -> opts { optVersion = True } ))
+      (printf "Include version in installation path, meaning: %s/PROJECT-VERSION %s"
+         (optPrefix defaultOptions) (defaultText . optVersion $ defaultOptions))
+   , Option ['V'] ["no-version"]
+      (NoArg (\opts -> opts { optVersion = False } ))
+      (printf "Do not include version in installation path, meaning: %s/PROJECT %s"
+         (optPrefix defaultOptions) (defaultText . not . optVersion $ defaultOptions))
+   ]
+
+
+defaultText :: Bool -> String
+defaultText True  = " Default"
+defaultText False = ""
+
+
+parseOpts :: [String] -> IO (Options, [String])
+parseOpts args =
+   case getOpt Permute options args of
+      (o,n,[]  ) -> return (foldl (flip id) defaultOptions o, n)
+      (_,_,errs) -> ioError $ userError (concat errs ++ usageText)
+
+
+usageText :: String
+usageText = (usageInfo header options) ++ "\n" ++ footer
+   where
+      header = init $ unlines
+         [ "Usage: install.hs [OPTIONS]"
+         , ""
+         , "options:"
+         ]
+      footer = init $ unlines
+         [ "INSTALLATION TYPE"
+         , ""
+         , "This is the topology used when copying files, one of: bundle, fhs"
+         , ""
+         , "bundle is sort-of a self-contained structure like this:"
+         , ""
+         , "  $PREFIX/"
+         , "    $PROJECT -> $PROJECT-$VERSION    <-- if --link was specified"
+         , "    $PROJECT-$VERSION/    <-- this is the \"app directory\""
+         , "      bin/..."
+         , "      doc/LICENSE"
+         , "      resources/..."
+         , ""
+         , "fhs is the more traditional UNIX structure like this:"
+         , ""
+         , "  $PREFIX/"
+         , "    bin/..."
+         , "    share/"
+         , "      $PROJECT-$VERSION/  <-- this is the \"app directory\""
+         , "        doc/LICENSE"
+         , "        resources/..."
+         , ""
+         , "Be aware that when the --delete switch is used along with fhs type, the binaries WILL NOT be deleted, only the \"app directory\"."
+         , ""
+         , "COMPILING"
+         , ""
+         , "install.hs was intentionally left as a script, but if you would prefer to compile it, do this:"
+         , ""
+         , "  $ stack ghc -- -o util/install util/install.hs"
+         , ""
+         , ""
+         , "This script is part of the hsinstall package by Dino Morelli <dino@ui3.info>"
+         ]
+
+
+{-
+   Recursive file copying code
+
+   It was desireable to have a standalone recursive file copy in
+   this script for maximum cross-platform compatibility and to
+   avoid Haskell library dependencies.
+
+   Many thanks to [abuzittin gillifirca](https://codereview.stackexchange.com/users/20251/abuzittin-gillifirca) for the StackOverflow post [Copying files in Haskell](https://codereview.stackexchange.com/questions/68908/copying-files-in-haskell) where the following code was lifted.
+-}
+
+copyTree :: Bool -> FilePath -> FilePath -> IO ()
+copyTree chatty s t = do
+    createDirectoryIfMissing True t
+    subItems <- getSubitems s
+    mapM_ (copyItem chatty s t) subItems
+
+
+getSubitems :: FilePath -> IO [(Bool, FilePath)]
+getSubitems path = getSubitems' ""
+  where
+    getChildren path =  (\\ [".", ".."]) <$> getDirectoryContents path
+
+    getSubitems' relPath = do
+        let absPath = path </> relPath
+        isDir <- doesDirectoryExist absPath
+        children <- if isDir then getChildren absPath else return []
+        let relChildren = [relPath </> p | p <- children]
+        ((isDir, relPath) :) . concat <$> mapM getSubitems' relChildren
+
+
+copyItem :: Bool -> FilePath -> FilePath -> (Bool, FilePath) -> IO ()
+copyItem chatty baseSourcePath baseTargetPath (isDir, relativePath) = do
+    let sourcePath = baseSourcePath </> relativePath
+    let targetPath = baseTargetPath </> relativePath
+
+    when chatty $
+       putStrLn $ "Copying " ++ sourcePath ++ " to " ++ targetPath
+
+    if isDir
+      then createDirectoryIfMissing False targetPath
+      else copyFile sourcePath targetPath
diff --git a/util/prefs/boring b/util/prefs/boring
--- a/util/prefs/boring
+++ b/util/prefs/boring
@@ -1,7 +1,7 @@
 # This file contains a list of extended regular expressions, one per
 # line. A file path matching any of these expressions will be filtered
 # out during `darcs add', or when the `--look-for-adds' flag is passed
-# to `darcs whatsnew' and `record'.  The entries in ~/.darcs/boring (if
+# to `darcs whatsnew' and `record'. The entries in ~/.darcs/boring (if
 # it exists) supplement those in this file.
 # 
 # Blank lines, and lines beginning with an octothorpe (#) are ignored.
@@ -45,11 +45,8 @@
 # cabal intermediates
 \.installed-pkg-config
 \.setup-config
-# cabal sandbox
-^\.cabal-sandbox(/|$)
-cabal\.sandbox\.config
 # standard cabal build dir, might not be boring for everybody
-^dist(/|$)
+# ^dist(/|$)
 # autotools
 (^|/)autom4te\.cache($|/)
 (^|/)config\.(log|status)$
@@ -121,3 +118,7 @@
 (^|/)\.lock-wscript$
 # mac os finder
 (^|/)\.DS_Store$
+# emacs saved sessions (desktops)
+(^|.*/)\.emacs\.desktop(\.lock)?$
+
+(^|/).stack-work($|/)
diff --git a/util/win-dist.sh b/util/win-dist.sh
--- a/util/win-dist.sh
+++ b/util/win-dist.sh
@@ -2,14 +2,14 @@
 
 version=$(awk '/^[Vv]ersion/ { print $2 }' epub-tools.cabal)
 
-buildDir="dist/build"
-
-binaries="$buildDir/epubmeta/epubmeta.exe $buildDir/epubname/epubname.exe $buildDir/epubzip/epubzip.exe"
+buildDir="dist"
 
-strip $binaries
+./util/install.hs -p $buildDir -t bundle
 
-zipFile="dist/epub-tools-$version-win.zip"
+zipFile="epub-tools-$version-win.zip"
 
 rm $zipFile
 
-zip -j $zipFile doc/INSTALL $binaries
+cd $buildDir
+
+zip -r ../$zipFile *
