diff --git a/HACKING.md b/HACKING.md
index 623086be03d53f39451c1a42d7783db26dfcfb61..d30433c1494ca266d1e2fd415a3df69cdf326f88 100644
--- a/HACKING.md
+++ b/HACKING.md
@@ -6,10 +6,6 @@
 
 This is an open variant, similar to [plucky](https://hackage.haskell.org/package/plucky) or [oops](https://github.com/i-am-tom/oops) and allows us to combine different error types. Maybe it is too much and it's a little bit [unergonomic](https://github.com/haskus/packages/issues/32) at times. If it really hurts maintenance, it will be removed. It was more of an experiment.
 
-### No use of filepath or directory
-
-Filepath and directory have two fundamental problems: 1. they use String as filepath (see [AFPP](https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/abstract-file-path) as to why this is wrong) and 2. they try very hard to be cross-platform at the expense of low-level correctness. Instead, we use the [hpath](https://github.com/hasufell/hpath) libraries for file and filepath related stuff, which also gives us stronger filepath types.
-
 ### No use of haskell-TLS
 
 I consider haskell-TLS an interesting experiment, but not a battle-tested and peer-reviewed crypto implementation. There is little to no research about what the intricacies of using haskell for low-level crypto are and how vulnerable such binaries are. Instead, we use either curl the binary (for FreeBSD and mac) or http-io-streams, which works with OpenSSL bindings.
diff --git a/README.md b/README.md
index 5a1999f62650533aa5e09f37cc1565dcee333c45..72254e1fd8187376768b8ee57d09bfe273d3abde 100644
--- a/README.md
+++ b/README.md
@@ -234,7 +234,7 @@ ghcup is not a reimplementation of stack. The only common part is automatic inst
 
 2. Why not support windows?
 
-Windows support is [WIP](https://gitlab.haskell.org/haskell/ghcup-hs/-/issues/130).
+We do.
 
 3. Why the haskell reimplementation?
 
diff --git a/app/ghcup-gen/Validate.hs b/app/ghcup-gen/Validate.hs
index fa8518bca252900b510f255874a9c57e1fdfcd78..9bdb3053daf1b33fc3b2ca11d8739eeffcbbe592 100644
--- a/app/ghcup-gen/Validate.hs
+++ b/app/ghcup-gen/Validate.hs
@@ -37,12 +37,11 @@ import           Data.IORef
 import           Data.List
 import           Data.String.Interpolate
 import           Data.Versions
-import           HPath                          ( toFilePath, rel )
 import           Haskus.Utils.Variant.Excepts
 import           Optics
+import           System.FilePath
 import           System.Exit
 import           System.IO
-import           System.Posix.FilePath
 import           Text.ParserCombinators.ReadP
 import           Text.PrettyPrint.HughesPJClass ( prettyShow )
 import           Text.Regex.Posix
@@ -238,7 +237,7 @@ validateTarballs (TarballFilter tool versionRegex) dls = do
       $ do
         case tool of
           Just GHCup -> do
-            let fn = [rel|ghcup|]
+            let fn = "ghcup"
             dir <- liftIO ghcupCacheDir
             p <- liftE $ download dli dir (Just fn)
             liftE $ checkDigest dli p
@@ -252,7 +251,7 @@ validateTarballs (TarballFilter tool versionRegex) dls = do
     case r of
       VRight (Just basePath) -> do
         case _dlSubdir dli of
-          Just (RealDir (toFilePath -> prel)) -> do
+          Just (RealDir prel) -> do
             lift $ $(logInfo)
               [i|verifying subdir: #{prel}|]
             when (basePath /= prel) $ do
diff --git a/app/ghcup/BrickMain.hs b/app/ghcup/BrickMain.hs
index d11003c453c21449c56485fabdfbc3f090d363b3..9ea6f75ae5270afeba9516a84f44b9a75962656e 100644
--- a/app/ghcup/BrickMain.hs
+++ b/app/ghcup/BrickMain.hs
@@ -14,6 +14,7 @@ import           GHCup.Download
 import           GHCup.Errors
 import           GHCup.Types
 import           GHCup.Utils
+import           GHCup.Utils.Prelude ( decUTF8Safe )
 import           GHCup.Utils.File
 import           GHCup.Utils.Logger
 
@@ -518,7 +519,8 @@ changelog' BrickState { appData = BrickData {..} } (_, ListResult {..}) = do
             Darwin  -> "open"
             Linux _ -> "xdg-open"
             FreeBSD -> "xdg-open"
-      exec cmd True [serializeURIRef' uri] Nothing Nothing >>= \case
+            Windows -> "start"
+      exec cmd [T.unpack $ decUTF8Safe $ serializeURIRef' uri] Nothing Nothing >>= \case
         Right _ -> pure $ Right ()
         Left  e -> pure $ Left $ prettyShow e
 
diff --git a/app/ghcup/Main.hs b/app/ghcup/Main.hs
index 4535958f53e594bc4ed0192b324c34f75db7f17a..306d842c0f3602e5f79268edadd69fb012b3ed18 100644
--- a/app/ghcup/Main.hs
+++ b/app/ghcup/Main.hs
@@ -53,8 +53,6 @@ import           Data.Versions           hiding ( str )
 import           Data.Void
 import           GHC.IO.Encoding
 import           Haskus.Utils.Variant.Excepts
-import           HPath
-import           HPath.IO
 import           Language.Haskell.TH
 import           Options.Applicative     hiding ( style )
 import           Options.Applicative.Help.Pretty ( text )
@@ -64,6 +62,7 @@ import           System.Console.Pretty   hiding ( color )
 import qualified System.Console.Pretty         as Pretty
 import           System.Environment
 import           System.Exit
+import           System.FilePath
 import           System.IO               hiding ( appendFile )
 import           Text.Read               hiding ( lift )
 import           Text.PrettyPrint.HughesPJClass ( prettyShow )
@@ -170,17 +169,17 @@ data CompileCommand = CompileGHC GHCCompileOptions
 
 data GHCCompileOptions = GHCCompileOptions
   { targetGhc    :: Either Version GitBranch
-  , bootstrapGhc :: Either Version (Path Abs)
+  , bootstrapGhc :: Either Version FilePath
   , jobs         :: Maybe Int
-  , buildConfig  :: Maybe (Path Abs)
-  , patchDir     :: Maybe (Path Abs)
+  , buildConfig  :: Maybe FilePath
+  , patchDir     :: Maybe FilePath
   , crossTarget  :: Maybe Text
   , addConfArgs  :: [Text]
   , setCompile   :: Bool
   }
 
 data UpgradeOpts = UpgradeInplace
-                 | UpgradeAt (Path Abs)
+                 | UpgradeAt FilePath
                  | UpgradeGHCupDir
                  deriving Show
 
@@ -721,8 +720,7 @@ ghcCompileOpts =
     <*> option
           (eitherReader
             (\x ->
-              (bimap (const "Not a valid version") Left . version . T.pack $ x)
-                <|> (bimap show Right . parseAbs . E.encodeUtf8 . T.pack $ x)
+              (bimap (const "Not a valid version") Left . version . T.pack $ x) <|> (if isPathSeparator (head x) then pure $ Right x else Left "Not an absolute Path")
             )
           )
           (  short 'b'
@@ -740,26 +738,14 @@ ghcCompileOpts =
           )
     <*> optional
           (option
-            (eitherReader
-              (\x ->
-                first show . parseAbs . E.encodeUtf8 . T.pack $ x :: Either
-                    String
-                    (Path Abs)
-              )
-            )
+            str
             (short 'c' <> long "config" <> metavar "CONFIG" <> help
               "Absolute path to build config file"
             )
           )
     <*> optional
           (option
-            (eitherReader
-              (\x ->
-                first show . parseAbs . E.encodeUtf8 . T.pack $ x :: Either
-                    String
-                    (Path Abs)
-              )
-            )
+            str
             (short 'p' <> long "patchdir" <> metavar "PATCH_DIR" <> help
               "Absolute path to patch directory (applied in order, uses -p1)"
             )
@@ -1040,13 +1026,7 @@ upgradeOptsP =
       )
     <|> (   UpgradeAt
         <$> option
-              (eitherReader
-                (\x ->
-                  first show . parseAbs . E.encodeUtf8 . T.pack $ x :: Either
-                      String
-                      (Path Abs)
-                )
-              )
+              str
               (short 't' <> long "target" <> metavar "TARGET_DIR" <> help
                 "Absolute filepath to write ghcup into"
               )
@@ -1058,9 +1038,9 @@ upgradeOptsP =
 describe_result :: String
 describe_result = $( LitE . StringL <$>
                      runIO (do
-                             CapturedProcess{..} <- executeOut [rel|git|] ["describe"] Nothing
+                             CapturedProcess{..} <- executeOut "git" ["describe"] Nothing
                              case _exitCode of
-                               ExitSuccess   -> pure . T.unpack . decUTF8Safe $ _stdOut
+                               ExitSuccess   -> pure . T.unpack . decUTF8Safe' $ _stdOut
                                ExitFailure _ -> pure numericVer
                      )
                    )
@@ -1114,7 +1094,7 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
           let loggerConfig = LoggerConfig
                 { lcPrintDebug = verbose settings
                 , colorOutter  = B.hPut stderr
-                , rawOutter    = appendFile logfile
+                , rawOutter    = B.appendFile logfile
                 }
           let runLogger = myLoggerT loggerConfig
 
@@ -1616,12 +1596,9 @@ Make sure to clean up #{tmpdir} afterwards.|])
 
             Upgrade uOpts force -> do
               target <- case uOpts of
-                UpgradeInplace -> do
-                  efp <- liftIO getExecutablePath
-                  p   <- parseAbs . E.encodeUtf8 . T.pack $ efp
-                  pure $ Just p
+                UpgradeInplace  -> Just <$> liftIO getExecutablePath
                 (UpgradeAt p)   -> pure $ Just p
-                UpgradeGHCupDir -> pure (Just (binDir </> [rel|ghcup|]))
+                UpgradeGHCupDir -> pure (Just (binDir </> "ghcup"))
 
               runUpgrade (liftE $ upgradeGHCup dls target force pfreq) >>= \case
                 VRight v' -> do
@@ -1677,12 +1654,12 @@ Make sure to clean up #{tmpdir} afterwards.|])
                               Darwin  -> "open"
                               Linux _ -> "xdg-open"
                               FreeBSD -> "xdg-open"
+                              Windows -> "start"
 
                   if clOpen
                     then
                       exec cmd
-                           True
-                           [serializeURIRef' uri]
+                           [T.unpack $ decUTF8Safe $ serializeURIRef' uri]
                            Nothing
                            Nothing
                         >>= \case
@@ -1977,10 +1954,10 @@ checkForUpdates dls pfreq = do
 prettyDebugInfo :: DebugInfo -> String
 prettyDebugInfo DebugInfo {..} = [i|Debug Info
 ==========
-GHCup base dir: #{toFilePath diBaseDir}
-GHCup bin dir: #{toFilePath diBinDir}
-GHCup GHC directory: #{toFilePath diGHCDir}
-GHCup cache directory: #{toFilePath diCacheDir}
+GHCup base dir: #{diBaseDir}
+GHCup bin dir: #{diBinDir}
+GHCup GHC directory: #{diGHCDir}
+GHCup cache directory: #{diCacheDir}
 Architecture: #{prettyShow diArch}
 Platform: #{prettyShow diPlatform}
 Version: #{describe_result}|]
diff --git a/cabal.project b/cabal.project
index 2e0cbc09ce186976fd672c3269d499810cd2d656..361aa936d864468b8ffc66c263d44342e18c2856 100644
--- a/cabal.project
+++ b/cabal.project
@@ -10,6 +10,11 @@ package ghcup
     tests: True
     flags: +tui
 
+source-repository-package
+    type: git
+    location: https://github.com/hasufell/tar-bytestring
+    tag: 9b5970ca6c924069498e95a8b59cb21e909a9ebe
+
 constraints: http-io-streams -brotli
 
 package libarchive
diff --git a/ghcup-0.0.4.yaml b/ghcup-0.0.4.yaml
index 0ea060ed73471f6437785454739508ec48c23a4b..7097842fb68576f4d949660dca527fdb6d8c3819 100644
--- a/ghcup-0.0.4.yaml
+++ b/ghcup-0.0.4.yaml
@@ -1524,6 +1524,11 @@ ghcupDownloads:
               dlUri: https://downloads.haskell.org/~ghc/9.0.1/ghc-9.0.1-x86_64-portbld-freebsd.tar.xz
               dlSubdir: ghc-9.0.1
               dlHash: 9dbc06d8832cae5c9f86dd7b2db729b3748a47beb4fd4b1e62bb66119817c3c1
+          Windows:
+            unknown_versioning:
+              dlUri: https://downloads.haskell.org/~ghc/9.0.1/ghc-9.0.1-windows-extra-src.tar.xz
+              dlSubdir: ghc-9.0.1
+              dlHash: 55e27d1907430cfba0f8b6c35c40dbb6f23b0a3518bfa5c9b453ba5cde86030c
         A_32:
           Linux_Debian:
             '( >= 9 && < 10 )': &ghc-901-32-deb9
diff --git a/ghcup.cabal b/ghcup.cabal
index c97c29933f567991652136ba22eabb082d5d3ab2..23f51af401ba3f434b26a90a8487e583729cebfd 100644
--- a/ghcup.cabal
+++ b/ghcup.cabal
@@ -58,6 +58,7 @@ library
     GHCup.Utils
     GHCup.Utils.Dirs
     GHCup.Utils.File
+    GHCup.Utils.File.Common
     GHCup.Utils.Logger
     GHCup.Utils.MegaParsec
     GHCup.Utils.Prelude
@@ -96,15 +97,14 @@ library
     , concurrent-output     ^>=1.10.11
     , containers            ^>=0.6
     , cryptohash-sha256     ^>=0.11.101.0
+    , deepseq
+    , directory
     , disk-free-space       ^>=0.1.0.1
+    , extra
+    , filepath
     , generics-sop          ^>=0.5
     , haskus-utils-types    ^>=1.5
     , haskus-utils-variant  >=3.0        && <3.2
-    , hpath                 >=0.11       && <0.13
-    , hpath-directory       ^>=0.14.1
-    , hpath-filepath        ^>=0.10.3
-    , hpath-io              ^>=0.14.1
-    , hpath-posix           ^>=0.13.2
     , lzma-static           ^>=5.2.5.2
     , megaparsec            >=8.0.0      && <9.1
     , monad-logger          ^>=0.3.31
@@ -115,6 +115,7 @@ library
     , parsec                ^>=3.1
     , pretty                ^>=1.1.3.1
     , pretty-terminal       ^>=0.1.0.0
+    , process
     , regex-posix           ^>=0.96
     , resourcet             ^>=1.2.2
     , safe                  ^>=0.3.18
@@ -122,22 +123,19 @@ library
     , split                 ^>=0.2.3.4
     , streamly              ^>=0.7.3
     , streamly-bytestring   ^>=0.1.2
-    , streamly-posix        ^>=0.1.0.0
     , strict-base           ^>=0.4
     , string-interpolate    >=0.2.0.0    && <0.4
+    , temporary
     , template-haskell      >=2.7        && <2.17
     , text                  ^>=1.2.4.0
     , time                  ^>=1.9.3
     , transformers          ^>=0.5
-    , unix                  ^>=2.7
-    , unix-bytestring       ^>=0.3
     , unliftio-core         ^>=0.2.0.1
     , unordered-containers  ^>=0.2.10.0
     , uri-bytestring        ^>=0.3.2.2
     , utf8-string           ^>=1.0
     , vector                ^>=0.12
     , versions              ^>=4.0.1
-    , vty                   >=5.28.2     && <5.34
     , word8                 ^>=0.1.3
     , yaml                  ^>=0.11.4.0
     , zlib                  ^>=0.6.2.2
@@ -153,11 +151,28 @@ library
 
   if flag(tar)
     cpp-options:   -DTAR
-    build-depends: tar-bytestring ^>=0.6.3.1
+    build-depends: tar
 
   else
     build-depends: libarchive ^>=3.0.0.0
 
+  if os(windows)
+    cpp-options:   -DIS_WINDOWS
+    other-modules: GHCup.Utils.File.Windows
+  else
+    build-depends:
+          unix                  ^>=2.7
+        , unix-bytestring
+        , hpath-posix
+        , streamly-posix        ^>=0.1.0.0
+    other-modules: GHCup.Utils.File.Posix
+
+  if flag(tui) && !os(windows)
+    cpp-options:   -DBRICK
+    build-depends:
+        vty     >=5.28.2 && <5.34
+
+
 executable ghcup
   main-is:            Main.hs
   hs-source-dirs:     app/ghcup
@@ -181,10 +196,9 @@ executable ghcup
     , base                  >=4.13     && <5
     , bytestring            ^>=0.10
     , containers            ^>=0.6
+    , filepath
     , ghcup
     , haskus-utils-variant  >=3.0      && <3.2
-    , hpath                 >=0.11     && <0.13
-    , hpath-io              ^>=0.14.1
     , megaparsec            >=8.0.0    && <9.1
     , monad-logger          ^>=0.3.31
     , mtl                   ^>=2.2
@@ -204,7 +218,7 @@ executable ghcup
   if flag(internal-downloader)
     cpp-options: -DINTERNAL_DOWNLOADER
 
-  if flag(tui)
+  if flag(tui) && !os(windows)
     cpp-options:   -DBRICK
     other-modules: BrickMain
     build-depends:
@@ -214,7 +228,6 @@ executable ghcup
 
   if flag(tar)
     cpp-options: -DTAR
-
   else
     build-depends: libarchive ^>=3.0.0.0
 
@@ -241,10 +254,9 @@ executable ghcup-gen
     , base                  >=4.13     && <5
     , bytestring            ^>=0.10
     , containers            ^>=0.6
+    , filepath
     , ghcup
     , haskus-utils-variant  >=3.0      && <3.2
-    , hpath                 >=0.11     && <0.13
-    , hpath-filepath        ^>=0.10.3
     , monad-logger          ^>=0.3.31
     , mtl                   ^>=2.2
     , optics                >=0.2      && <0.5
@@ -264,7 +276,7 @@ executable ghcup-gen
 
   if flag(tar)
     cpp-options:   -DTAR
-    build-depends: tar-bytestring ^>=0.6.3.1
+    build-depends: tar
 
   else
     build-depends: libarchive ^>=3.0.0.0
@@ -297,7 +309,6 @@ test-suite ghcup-test
     , containers                ^>=0.6
     , generic-arbitrary         ^>=0.1.0
     , ghcup
-    , hpath                     >=0.11    && <0.13
     , hspec                     ^>=2.7.4
     , hspec-golden-aeson        >=0.7     && <0.10
     , QuickCheck                ^>=2.14.1
diff --git a/lib/GHCup.hs b/lib/GHCup.hs
index 71a6a88bf7b7c406b258aecfbf589dfdce96a534..ebafc1c927ce21034b4bb084f542073e8075e802 100644
--- a/lib/GHCup.hs
+++ b/lib/GHCup.hs
@@ -18,7 +18,7 @@ Copyright   : (c) Julian Ospald, 2020
 License     : LGPL-3.0
 Maintainer  : hasufell@hasufell.de
 Stability   : experimental
-Portability : POSIX
+Portability : portable
 
 This module contains the main functions that correspond
 to the command line interface, like installation, listing versions
@@ -58,6 +58,7 @@ import           Control.Monad.Trans.Resource
 import           Data.ByteString                ( ByteString )
 import           Data.Either
 import           Data.List
+import           Data.List.Extra
 import           Data.Maybe
 import           Data.String                    ( fromString )
 import           Data.String.Interpolate
@@ -65,10 +66,7 @@ import           Data.Text                      ( Text )
 import           Data.Time.Clock
 import           Data.Time.Format.ISO8601
 import           Data.Versions
-import           Data.Word8
 import           GHC.IO.Exception
-import           HPath
-import           HPath.IO                hiding ( hideError )
 import           Haskus.Utils.Variant.Excepts
 import           Optics
 import           Prelude                 hiding ( abs
@@ -76,10 +74,10 @@ import           Prelude                 hiding ( abs
                                                 , writeFile
                                                 )
 import           Safe                    hiding ( at )
+import           System.Directory        hiding ( findFiles )
+import           System.Environment
+import           System.FilePath
 import           System.IO.Error
-import           System.Posix.Env.ByteString    ( getEnvironment, getEnv )
-import           System.Posix.FilePath          ( getSearchPath, takeExtension )
-import           System.Posix.Files.ByteString
 import           Text.PrettyPrint.HughesPJClass ( prettyShow )
 import           Text.Regex.Posix
 
@@ -149,7 +147,7 @@ installGHCBindist dlinfo ver pfreq = do
 
  where
   toolchainSanityChecks = do
-    r <- forM ["CC", "LD"] (liftIO . getEnv)
+    r <- forM ["CC", "LD"] (liftIO . lookupEnv)
     case catMaybes r of
       [] -> pure ()
       _ -> do
@@ -168,9 +166,9 @@ installPackedGHC :: ( MonadMask m
                     , MonadIO m
                     , MonadUnliftIO m
                     )
-                 => Path Abs          -- ^ Path to the packed GHC bindist
+                 => FilePath          -- ^ Path to the packed GHC bindist
                  -> Maybe TarDir      -- ^ Subdir of the archive
-                 -> Path Abs          -- ^ Path to install to
+                 -> FilePath          -- ^ Path to install to
                  -> Version           -- ^ The GHC version
                  -> PlatformRequest
                  -> Excepts
@@ -204,18 +202,17 @@ installUnpackedGHC :: ( MonadReader AppState m
                       , MonadLogger m
                       , MonadIO m
                       )
-                   => Path Abs      -- ^ Path to the unpacked GHC bindist (where the configure script resides)
-                   -> Path Abs      -- ^ Path to install to
+                   => FilePath      -- ^ Path to the unpacked GHC bindist (where the configure script resides)
+                   -> FilePath      -- ^ Path to install to
                    -> Version       -- ^ The GHC version
                    -> PlatformRequest
                    -> Excepts '[ProcessError] m ()
 installUnpackedGHC path inst ver PlatformRequest{..} = do
   lift $ $(logInfo) "Installing GHC (this may take a while)"
   lEM $ execLogged "./configure"
-                   False
-                   (("--prefix=" <> toFilePath inst) : alpineArgs)
-                   [rel|ghc-configure|]
+                   (("--prefix=" <> inst) : alpineArgs)
                    (Just path)
+                   "ghc-configure"
                    Nothing
   lEM $ make ["install"] (Just path)
   pure ()
@@ -301,9 +298,9 @@ installCabalBindist dlinfo ver PlatformRequest {..} = do
   whenM
       (lift (cabalInstalled ver) >>= \a -> liftIO $
         handleIO (\_ -> pure False)
-          $ fmap (\x -> a && isSymbolicLink x)
+          $ fmap (\x -> a && x)
           -- ignore when the installation is a legacy cabal (binary, not symlink)
-          $ getSymbolicLinkStatus (toFilePath (binDir </> [rel|cabal|]))
+          $ pathIsSymbolicLink (binDir </> "cabal")
       )
       (throwE $ AlreadyInstalled Cabal ver)
 
@@ -328,19 +325,18 @@ installCabalBindist dlinfo ver PlatformRequest {..} = do
  where
   -- | Install an unpacked cabal distribution.
   installCabal' :: (MonadLogger m, MonadCatch m, MonadIO m)
-                => Path Abs      -- ^ Path to the unpacked cabal bindist (where the executable resides)
-                -> Path Abs      -- ^ Path to install to
+                => FilePath      -- ^ Path to the unpacked cabal bindist (where the executable resides)
+                -> FilePath      -- ^ Path to install to
                 -> Excepts '[CopyError] m ()
   installCabal' path inst = do
     lift $ $(logInfo) "Installing cabal"
-    let cabalFile = [rel|cabal|]
+    let cabalFile = "cabal"
     liftIO $ createDirRecursive' inst
-    destFileName <- lift $ parseRel (toFilePath cabalFile <> "-" <> verToBS ver)
+    let destFileName = cabalFile <> "-" <> T.unpack (prettyVer ver)
     let destPath = inst </> destFileName
     handleIO (throwE . CopyError . show) $ liftIO $ copyFile
       (path </> cabalFile)
       destPath
-      Overwrite
     lift $ chmod_755 destPath
 
 
@@ -437,8 +433,8 @@ installHLSBindist dlinfo ver PlatformRequest{..} = do
  where
   -- | Install an unpacked hls distribution.
   installHLS' :: (MonadFail m, MonadLogger m, MonadCatch m, MonadIO m)
-                => Path Abs      -- ^ Path to the unpacked hls bindist (where the executable resides)
-                -> Path Abs      -- ^ Path to install to
+                => FilePath      -- ^ Path to the unpacked hls bindist (where the executable resides)
+                -> FilePath      -- ^ Path to install to
                 -> Excepts '[CopyError] m ()
   installHLS' path inst = do
     lift $ $(logInfo) "Installing HLS"
@@ -452,20 +448,18 @@ installHLSBindist dlinfo ver PlatformRequest{..} = do
                      ([s|^haskell-language-server-[0-9].*$|] :: ByteString)
       )
     forM_ bins $ \f -> do
-      toF <- parseRel (toFilePath f <> "~" <> verToBS ver)
+      let toF = f <> "~" <> T.unpack (prettyVer ver)
       handleIO (throwE . CopyError . show) $ liftIO $ copyFile
         (path </> f)
         (inst </> toF)
-        Overwrite
       lift $ chmod_755 (inst </> toF)
 
     -- install haskell-language-server-wrapper
-    let wrapper = [rel|haskell-language-server-wrapper|]
-    toF <- parseRel (toFilePath wrapper <> "-" <> verToBS ver)
+    let wrapper = "haskell-language-server-wrapper"
+        toF = wrapper <> "-" <> T.unpack (prettyVer ver)
     handleIO (throwE . CopyError . show) $ liftIO $ copyFile
       (path </> wrapper)
       (inst </> toF)
-      Overwrite
     lift $ chmod_755 (inst </> toF)
 
 
@@ -596,19 +590,18 @@ installStackBindist dlinfo ver PlatformRequest {..} = do
  where
   -- | Install an unpacked stack distribution.
   installStack' :: (MonadLogger m, MonadCatch m, MonadIO m)
-                => Path Abs      -- ^ Path to the unpacked stack bindist (where the executable resides)
-                -> Path Abs      -- ^ Path to install to
+                => FilePath      -- ^ Path to the unpacked stack bindist (where the executable resides)
+                -> FilePath      -- ^ Path to install to
                 -> Excepts '[CopyError] m ()
   installStack' path inst = do
     lift $ $(logInfo) "Installing stack"
-    let stackFile = [rel|stack|]
+    let stackFile = "stack"
     liftIO $ createDirRecursive' inst
-    destFileName <- lift $ parseRel (toFilePath stackFile <> "-" <> verToBS ver)
+    let destFileName = stackFile <> "-" <> T.unpack (prettyVer ver)
     let destPath = inst </> destFileName
     handleIO (throwE . CopyError . show) $ liftIO $ copyFile
       (path </> stackFile)
       destPath
-      Overwrite
     lift $ chmod_755 destPath
 
 
@@ -640,7 +633,7 @@ setGHC :: ( MonadReader AppState m
        -> SetGHC
        -> Excepts '[NotInstalled] m GHCTargetVersion
 setGHC ver sghc = do
-  let verBS = verToBS (_tvVersion ver)
+  let verS = T.unpack $ prettyVer (_tvVersion ver)
   ghcdir                        <- lift $ ghcupGHCDir ver
 
   whenM (lift $ not <$> ghcInstalled ver) (throwE (NotInstalled GHC ver))
@@ -662,49 +655,47 @@ setGHC ver sghc = do
     mTargetFile <- case sghc of
       SetGHCOnly -> pure $ Just file
       SetGHC_XY  -> do
-        v' <-
-          handle
+        handle
             (\(e :: ParseError) -> lift $ $(logWarn) [i|#{e}|] >> pure Nothing)
-          $ fmap Just
-          $ getMajorMinorV (_tvVersion ver)
-        forM v' $ \(mj, mi) ->
-          let major' = E.encodeUtf8 $ intToText mj <> "." <> intToText mi
-          in  parseRel (toFilePath file <> B.singleton _hyphen <> major')
+          $ do 
+            (mj, mi) <- getMajorMinorV (_tvVersion ver)
+            let major' = intToText mj <> "." <> intToText mi
+            pure $ Just (file <> "-" <> T.unpack major')
       SetGHC_XYZ ->
-        fmap Just $ parseRel (toFilePath file <> B.singleton _hyphen <> verBS)
+        pure $ Just (file <> "-" <> verS)
 
     -- create symlink
     forM mTargetFile $ \targetFile -> do
       let fullF = binDir </> targetFile
-      destL <- lift $ ghcLinkDestination (toFilePath file) ver
-      lift $ $(logDebug) [i|ln -s #{destL} #{toFilePath fullF}|]
-      liftIO $ createSymlink fullF destL
+      destL <- lift $ ghcLinkDestination file ver
+      lift $ $(logDebug) [i|ln -s #{destL} #{fullF}|]
+      liftIO $ createFileLink destL fullF
 
   -- create symlink for share dir
-  when (isNothing . _tvTarget $ ver) $ lift $ symlinkShareDir ghcdir verBS
+  when (isNothing . _tvTarget $ ver) $ lift $ symlinkShareDir ghcdir verS
 
   pure ver
 
  where
 
   symlinkShareDir :: (MonadReader AppState m, MonadIO m, MonadLogger m)
-                  => Path Abs
-                  -> ByteString
+                  => FilePath
+                  -> String
                   -> m ()
-  symlinkShareDir ghcdir verBS = do
+  symlinkShareDir ghcdir ver' = do
     AppState { dirs = Dirs {..} } <- ask
     let destdir = baseDir
     case sghc of
       SetGHCOnly -> do
-        let sharedir     = [rel|share|]
+        let sharedir     = "share"
         let fullsharedir = ghcdir </> sharedir
         whenM (liftIO $ doesDirectoryExist fullsharedir) $ do
           let fullF   = destdir </> sharedir
-          let targetF = "./ghc/" <> verBS <> "/" <> toFilePath sharedir
+          let targetF = "./ghc/" <> ver' <> "/" <> sharedir
           $(logDebug) [i|rm -f #{fullF}|]
-          liftIO $ hideError doesNotExistErrorType $ deleteFile fullF
+          liftIO $ hideError doesNotExistErrorType $ removeFile fullF
           $(logDebug) [i|ln -s #{targetF} #{fullF}|]
-          liftIO $ createSymlink fullF targetF
+          liftIO $ createFileLink targetF fullF
       _ -> pure ()
 
 
@@ -714,8 +705,7 @@ setCabal :: (MonadReader AppState m, MonadLogger m, MonadThrow m, MonadFail m, M
          => Version
          -> Excepts '[NotInstalled] m ()
 setCabal ver = do
-  let verBS = verToBS ver
-  targetFile <- parseRel ("cabal-" <> verBS)
+  let targetFile = "cabal-" <> T.unpack (prettyVer ver)
 
   -- symlink destination
   AppState {dirs = Dirs {..}} <- lift ask
@@ -725,17 +715,17 @@ setCabal ver = do
     $ throwE
     $ NotInstalled Cabal (GHCTargetVersion Nothing ver)
 
-  let cabalbin = binDir </> [rel|cabal|]
+  let cabalbin = binDir </> "cabal"
 
   -- delete old file (may be binary or symlink)
-  lift $ $(logDebug) [i|rm -f #{toFilePath cabalbin}|]
-  liftIO $ hideError doesNotExistErrorType $ deleteFile
+  lift $ $(logDebug) [i|rm -f #{cabalbin}|]
+  liftIO $ hideError doesNotExistErrorType $ removeFile
     cabalbin
 
   -- create symlink
-  let destL = toFilePath targetFile
-  lift $ $(logDebug) [i|ln -s #{destL} #{toFilePath cabalbin}|]
-  liftIO $ createSymlink cabalbin destL
+  let destL = targetFile
+  lift $ $(logDebug) [i|ln -s #{destL} #{cabalbin}|]
+  liftIO $ createFileLink destL cabalbin
 
   pure ()
 
@@ -760,32 +750,32 @@ setHLS ver = do
   -- selected version, so we could end up with stray or incorrect symlinks.
   oldSyms <- lift hlsSymlinks
   forM_ oldSyms $ \f -> do
-    lift $ $(logDebug) [i|rm #{toFilePath (binDir </> f)}|]
-    liftIO $ deleteFile (binDir </> f)
+    lift $ $(logDebug) [i|rm #{binDir </> f}|]
+    liftIO $ removeFile (binDir </> f)
 
   -- set haskell-language-server-<ghcver> symlinks
   bins <- lift $ hlsServerBinaries ver
   when (null bins) $ throwE $ NotInstalled HLS (GHCTargetVersion Nothing ver)
 
   forM_ bins $ \f -> do
-    let destL = toFilePath f
-    target <- parseRel . head . B.split _tilde . toFilePath $ f
+    let destL = f
+    let target = head . splitOn "~" $ f
 
-    lift $ $(logDebug) [i|rm -f #{toFilePath (binDir </> target)}|]
-    liftIO $ hideError doesNotExistErrorType $ deleteFile (binDir </> target)
+    lift $ $(logDebug) [i|rm -f #{binDir </> target}|]
+    liftIO $ hideError doesNotExistErrorType $ removeFile (binDir </> target)
 
-    lift $ $(logDebug) [i|ln -s #{destL} #{toFilePath (binDir </> target)}|]
-    liftIO $ createSymlink (binDir </> target) destL
+    lift $ $(logDebug) [i|ln -s #{destL} #{binDir </> target}|]
+    liftIO $ createFileLink destL (binDir </> target)
 
   -- set haskell-language-server-wrapper symlink
-  let destL = "haskell-language-server-wrapper-" <> verToBS ver
-  let wrapper = binDir </> [rel|haskell-language-server-wrapper|]
+  let destL = "haskell-language-server-wrapper-" <> T.unpack (prettyVer ver)
+  let wrapper = binDir </> "haskell-language-server-wrapper"
 
-  lift $ $(logDebug) [i|rm -f #{toFilePath wrapper}|]
-  liftIO $ hideError doesNotExistErrorType $ deleteFile wrapper
+  lift $ $(logDebug) [i|rm -f #{wrapper}|]
+  liftIO $ hideError doesNotExistErrorType $ removeFile wrapper
 
-  lift $ $(logDebug) [i|ln -s #{destL} #{toFilePath wrapper}|]
-  liftIO $ createSymlink wrapper destL
+  lift $ $(logDebug) [i|ln -s #{destL} #{wrapper}|]
+  liftIO $ createFileLink destL wrapper
 
   pure ()
 
@@ -795,8 +785,7 @@ setStack :: (MonadReader AppState m, MonadLogger m, MonadThrow m, MonadFail m, M
          => Version
          -> Excepts '[NotInstalled] m ()
 setStack ver = do
-  let verBS = verToBS ver
-  targetFile <- parseRel ("stack-" <> verBS)
+  let targetFile = "stack-" <> T.unpack (prettyVer ver)
 
   -- symlink destination
   AppState {dirs = Dirs {..}} <- lift ask
@@ -806,17 +795,16 @@ setStack ver = do
     $ throwE
     $ NotInstalled Stack (GHCTargetVersion Nothing ver)
 
-  let stackbin = binDir </> [rel|stack|]
+  let stackbin = binDir </> "stack"
 
   -- delete old file (may be binary or symlink)
-  lift $ $(logDebug) [i|rm -f #{toFilePath stackbin}|]
-  liftIO $ hideError doesNotExistErrorType $ deleteFile
+  lift $ $(logDebug) [i|rm -f #{stackbin}|]
+  liftIO $ hideError doesNotExistErrorType $ removeFile
     stackbin
 
   -- create symlink
-  let destL = toFilePath targetFile
-  lift $ $(logDebug) [i|ln -s #{destL} #{toFilePath stackbin}|]
-  liftIO $ createSymlink stackbin destL
+  lift $ $(logDebug) [i|ln -s #{targetFile} #{stackbin}|]
+  liftIO $ createFileLink targetFile stackbin
 
   pure ()
 
@@ -948,13 +936,13 @@ listVersions av lt' criteria pfreq = do
           }
       Left e -> do
         $(logWarn)
-          [i|Could not parse version of stray directory #{toFilePath e}|]
+          [i|Could not parse version of stray directory #{e}|]
         pure Nothing
 
   strayCabals :: (MonadReader AppState m, MonadCatch m, MonadThrow m, MonadLogger m, MonadIO m)
             => Map.Map Version [Tag]
             -> Maybe Version
-            -> [Either (Path Rel) Version]
+            -> [Either FilePath Version]
             -> m [ListResult]
   strayCabals avTools cSet cabals = do
     fmap catMaybes $ forM cabals $ \case
@@ -977,7 +965,7 @@ listVersions av lt' criteria pfreq = do
               }
       Left e -> do
         $(logWarn)
-          [i|Could not parse version of stray directory #{toFilePath e}|]
+          [i|Could not parse version of stray directory #{e}|]
         pure Nothing
 
   strayHLS :: (MonadReader AppState m, MonadCatch m, MonadThrow m, MonadLogger m, MonadIO m)
@@ -1005,7 +993,7 @@ listVersions av lt' criteria pfreq = do
               }
       Left e -> do
         $(logWarn)
-          [i|Could not parse version of stray directory #{toFilePath e}|]
+          [i|Could not parse version of stray directory #{e}|]
         pure Nothing
 
   strayStacks :: (MonadReader AppState m, MonadCatch m, MonadThrow m, MonadLogger m, MonadIO m)
@@ -1033,18 +1021,18 @@ listVersions av lt' criteria pfreq = do
               }
       Left e -> do
         $(logWarn)
-          [i|Could not parse version of stray directory #{toFilePath e}|]
+          [i|Could not parse version of stray directory #{e}|]
         pure Nothing
 
   -- NOTE: this are not cross ones, because no bindists
   toListResult :: (MonadLogger m, MonadReader AppState m, MonadIO m, MonadCatch m)
                => Tool
                -> Maybe Version
-               -> [Either (Path Rel) Version]
+               -> [Either FilePath Version]
                -> Maybe Version
-               -> [Either (Path Rel) Version]
+               -> [Either FilePath Version]
                -> Maybe Version
-               -> [Either (Path Rel) Version]
+               -> [Either FilePath Version]
                -> (Version, [Tag])
                -> m ListResult
   toListResult t cSet cabals hlsSet' hlses stackSet' stacks (v, tags) = case t of
@@ -1156,8 +1144,8 @@ rmGHCVer ver = do
   handle (\(_ :: ParseError) -> pure ()) $ liftE $ rmMajorSymlinks ver
   -- then fix them (e.g. with an earlier version)
 
-  lift $ $(logInfo) [i|Removing directory recursively: #{toFilePath dir}|]
-  liftIO $ deleteDirRecursive dir
+  lift $ $(logInfo) [i|Removing directory recursively: #{dir}|]
+  liftIO $ removeDirectoryRecursive dir
 
   v' <-
     handle
@@ -1171,7 +1159,7 @@ rmGHCVer ver = do
 
   liftIO
     $ hideError doesNotExistErrorType
-    $ deleteFile (baseDir </> [rel|share|])
+    $ removeFile (baseDir </> "share")
 
 
 -- | Delete a cabal version. Will try to fix the @cabal@ symlink
@@ -1186,15 +1174,15 @@ rmCabalVer ver = do
 
   AppState {dirs = Dirs {..}} <- lift ask
 
-  cabalFile <- lift $ parseRel ("cabal-" <> verToBS ver)
-  liftIO $ hideError doesNotExistErrorType $ deleteFile (binDir </> cabalFile)
+  let cabalFile = ("cabal-" <> T.unpack (prettyVer ver))
+  liftIO $ hideError doesNotExistErrorType $ removeFile (binDir </> cabalFile)
 
   when (Just ver == cSet) $ do
     cVers <- lift $ fmap rights getInstalledCabals
     case headMay . reverse . sort $ cVers of
       Just latestver -> setCabal latestver
-      Nothing        -> liftIO $ hideError doesNotExistErrorType $ deleteFile
-        (binDir </> [rel|cabal|])
+      Nothing        -> liftIO $ hideError doesNotExistErrorType $ removeFile
+        (binDir </> "cabal")
 
 
 -- | Delete a hls version. Will try to fix the hls symlinks
@@ -1210,14 +1198,14 @@ rmHLSVer ver = do
   AppState {dirs = Dirs {..}} <- lift ask
 
   bins <- lift $ hlsAllBinaries ver
-  forM_ bins $ \f -> liftIO $ deleteFile (binDir </> f)
+  forM_ bins $ \f -> liftIO $ removeFile (binDir </> f)
 
   when (Just ver == isHlsSet) $ do
     -- delete all set symlinks
     oldSyms <- lift hlsSymlinks
     forM_ oldSyms $ \f -> do
-      lift $ $(logDebug) [i|rm #{toFilePath (binDir </> f)}|]
-      liftIO $ deleteFile (binDir </> f)
+      lift $ $(logDebug) [i|rm #{binDir </> f}|]
+      liftIO $ removeFile (binDir </> f)
     -- set latest hls
     hlsVers <- lift $ fmap rights getInstalledHLSs
     case headMay . reverse . sort $ hlsVers of
@@ -1237,15 +1225,15 @@ rmStackVer ver = do
 
   AppState {dirs = Dirs {..}} <- lift ask
 
-  stackFile <- lift $ parseRel ("stack-" <> verToBS ver)
-  liftIO $ hideError doesNotExistErrorType $ deleteFile (binDir </> stackFile)
+  let stackFile = ("stack-" <> T.unpack (prettyVer ver))
+  liftIO $ hideError doesNotExistErrorType $ removeFile (binDir </> stackFile)
 
   when (Just ver == sSet) $ do
     sVers <- lift $ fmap rights getInstalledStacks
     case headMay . reverse . sort $ sVers of
       Just latestver -> setStack latestver
-      Nothing        -> liftIO $ hideError doesNotExistErrorType $ deleteFile
-        (binDir </> [rel|stack|])
+      Nothing        -> liftIO $ hideError doesNotExistErrorType $ removeFile
+        (binDir </> "stack")
 
 
 
@@ -1290,10 +1278,10 @@ compileGHC :: ( MonadMask m
               )
            => GHCupDownloads
            -> Either GHCTargetVersion GitBranch          -- ^ version to install
-           -> Either Version (Path Abs)  -- ^ version to bootstrap with
+           -> Either Version FilePath  -- ^ version to bootstrap with
            -> Maybe Int                  -- ^ jobs
-           -> Maybe (Path Abs)           -- ^ build config
-           -> Maybe (Path Abs)           -- ^ patch directory
+           -> Maybe FilePath           -- ^ build config
+           -> Maybe FilePath           -- ^ patch directory
            -> [Text]                     -- ^ additional args to ./configure
            -> PlatformRequest
            -> Excepts
@@ -1341,7 +1329,7 @@ compileGHC dls targetGhc bstrap jobs mbuildConfig patchdir aargs pfreq@PlatformR
       -- clone from git
       Right GitBranch{..} -> do
         tmpUnpack <- lift mkGhcupTmpDir
-        let git args = execLogged [s|git|] True ("--no-pager":args) [rel|git|] (Just tmpUnpack) Nothing
+        let git args = execLogged "git" ("--no-pager":args) (Just tmpUnpack) "git" Nothing
         tver <- reThrowAll @_ @'[ProcessError] DownloadFailed $ do
           let rep = fromMaybe "https://gitlab.haskell.org/ghc/ghc.git" repo
           lift $ $(logInfo) [i|Fetching git repo #{rep} at ref #{ref} (this may take a while)|]
@@ -1362,13 +1350,13 @@ compileGHC dls targetGhc bstrap jobs mbuildConfig patchdir aargs pfreq@PlatformR
 
           lEM $ git [ "checkout", "FETCH_HEAD" ]
           lEM $ git [ "submodule", "update", "--init", "--depth", "1" ]
-          lEM $ execLogged "./boot" False [] [rel|ghc-bootstrap|] (Just tmpUnpack) Nothing
-          lEM $ execLogged "./configure" False [] [rel|ghc-bootstrap|] (Just tmpUnpack) Nothing
+          lEM $ execLogged "./boot" [] (Just tmpUnpack) "ghc-bootstrap" Nothing
+          lEM $ execLogged "./configure" [] (Just tmpUnpack) "ghc-bootstrap" Nothing
           CapturedProcess {..} <- liftIO $ makeOut
             ["show!", "--quiet", "VALUE=ProjectVersion" ] (Just tmpUnpack)
           case _exitCode of
-            ExitSuccess -> throwEither . MP.parse ghcProjectVersion "" . decUTF8Safe $ _stdOut
-            ExitFailure c -> fail ("Could not figure out GHC project version. Exit code was: " <> show c <> ". Error was: " <> T.unpack (decUTF8Safe _stdErr))
+            ExitSuccess -> throwEither . MP.parse ghcProjectVersion "" . decUTF8Safe' $ _stdOut
+            ExitFailure c -> fail ("Could not figure out GHC project version. Exit code was: " <> show c <> ". Error was: " <> T.unpack (decUTF8Safe' _stdErr))
 
         void $ liftIO $ darwinNotarization _rPlatform tmpUnpack
         lift $ $(logInfo) [i|Git version #{ref} corresponds to GHC version #{prettyVer tver}|]
@@ -1387,14 +1375,14 @@ compileGHC dls targetGhc bstrap jobs mbuildConfig patchdir aargs pfreq@PlatformR
 
     bghc <- case bstrap of
       Right g    -> pure $ Right g
-      Left  bver -> Left <$> parseRel ("ghc-" <> verToBS bver)
+      Left  bver -> pure $ Left ("ghc-" <> (T.unpack . prettyVer $ bver))
 
     (bindist, bmk) <- liftE $ runBuildAction
       tmpUnpack
       Nothing
       (do
         b <- compileBindist bghc tver workdir
-        bmk <- liftIO $ readFileStrict (build_mk workdir)
+        bmk <- liftIO $ B.readFile (build_mk workdir)
         pure (b, bmk)
       )
 
@@ -1407,7 +1395,7 @@ compileGHC dls targetGhc bstrap jobs mbuildConfig patchdir aargs pfreq@PlatformR
                              (tver ^. tvVersion)
                              pfreq
 
-    liftIO $ writeFile (ghcdir </> ghcUpSrcBuiltFile) (Just newFilePerms) bmk
+    liftIO $ B.writeFile (ghcdir </> ghcUpSrcBuiltFile) bmk
 
     reThrowAll GHCupSetError $ postGHCInstall tver
 
@@ -1439,13 +1427,13 @@ HADDOCK_DOCS = YES|]
                     , MonadIO m
                     , MonadFail m
                     )
-                 => Either (Path Rel) (Path Abs)
+                 => Either FilePath FilePath
                  -> GHCTargetVersion
-                 -> Path Abs
+                 -> FilePath
                  -> Excepts
                       '[FileDoesNotExistError, InvalidBuildConfig, PatchFailed, ProcessError, NotFoundInPATH, CopyError]
                       m
-                      (Path Abs)  -- ^ output path of bindist
+                      FilePath  -- ^ output path of bindist
   compileBindist bghc tver workdir = do
     lift $ $(logInfo) [i|configuring build|]
     liftE checkBuildConfig
@@ -1460,41 +1448,39 @@ HADDOCK_DOCS = YES|]
           bghcPath <- case bghc of
             Right ghc' -> pure ghc'
             Left  bver -> do
-              spaths <- catMaybes . fmap parseAbs <$> liftIO getSearchPath
+              spaths <- liftIO getSearchPath
               liftIO (searchPath spaths bver) !? NotFoundInPATH bver
           lEM $ execLogged
             "./configure"
-            False
             (  maybe mempty
-                      (\x -> ["--target=" <> E.encodeUtf8 x])
+                      (\x -> ["--target=" <> T.unpack x])
                       (_tvTarget tver)
-            ++ fmap E.encodeUtf8 aargs
+            ++ fmap T.unpack aargs
             )
-            [rel|ghc-conf|]
             (Just workdir)
-            (Just (("GHC", toFilePath bghcPath) : cEnv))
+            "ghc-conf"
+            (Just (("GHC", bghcPath) : cEnv))
        | otherwise -> do
         lEM $ execLogged
           "./configure"
-          False
-          (  [ "--with-ghc=" <> either toFilePath toFilePath bghc
+          (  [ "--with-ghc=" <> either id id bghc
              ]
           ++ maybe mempty
-                   (\x -> ["--target=" <> E.encodeUtf8 x])
+                   (\x -> ["--target=" <> T.unpack x])
                    (_tvTarget tver)
-          ++ fmap E.encodeUtf8 aargs
+          ++ fmap T.unpack aargs
           )
-          [rel|ghc-conf|]
           (Just workdir)
+          "ghc-conf"
           (Just cEnv)
 
     case mbuildConfig of
       Just bc -> liftIOException
         doesNotExistErrorType
-        (FileDoesNotExistError $ toFilePath bc)
-        (liftIO $ copyFile bc (build_mk workdir) Overwrite)
+        (FileDoesNotExistError bc)
+        (liftIO $ copyFile bc (build_mk workdir))
       Nothing ->
-        liftIO $ writeFile (build_mk workdir) (Just newFilePerms) defaultConf
+        liftIO $ B.writeFile (build_mk workdir) defaultConf
 
     lift $ $(logInfo) [i|Building (this may take a while)...|]
     lEM $ make (maybe [] (\j -> ["-j" <> fS (show j)]) jobs) (Just workdir)
@@ -1507,7 +1493,7 @@ HADDOCK_DOCS = YES|]
                      execBlank
                      ([s|^ghc-.*\.tar\..*$|] :: ByteString)
       )
-    c       <- liftIO $ readFile (workdir </> tar)
+    c       <- liftIO $ BL.readFile (workdir </> tar)
     cDigest <-
       fmap (T.take 8)
       . lift
@@ -1517,17 +1503,14 @@ HADDOCK_DOCS = YES|]
       . SHA256.hashlazy
       $ c
     cTime <- liftIO getCurrentTime
-    tarName <-
-      parseRel
-        [i|ghc-#{tVerToText tver}-#{pfReqToString pfreq}-#{iso8601Show cTime}-#{cDigest}.tar#{takeExtension (toFilePath tar)}|]
+    let tarName = [i|ghc-#{tVerToText tver}-#{pfReqToString pfreq}-#{iso8601Show cTime}-#{cDigest}.tar#{takeExtension tar}|]
     let tarPath = cacheDir </> tarName
     handleIO (throwE . CopyError . show) $ liftIO $ copyFile (workdir </> tar)
                                                              tarPath
-                                                             Strict
     lift $ $(logInfo) [i|Copied bindist to #{tarPath}|]
     pure tarPath
 
-  build_mk workdir = workdir </> [rel|mk/build.mk|]
+  build_mk workdir = workdir </> "mk/build.mk"
 
   checkBuildConfig :: (MonadCatch m, MonadIO m)
                    => Excepts
@@ -1537,10 +1520,10 @@ HADDOCK_DOCS = YES|]
   checkBuildConfig = do
     c <- case mbuildConfig of
       Just bc -> do
-        BL.toStrict <$> liftIOException
+        liftIOException
           doesNotExistErrorType
-          (FileDoesNotExistError $ toFilePath bc)
-          (liftIO $ readFile bc)
+          (FileDoesNotExistError bc)
+          (liftIO $ B.readFile bc)
       Nothing -> pure defaultConf
     let lines' = fmap T.strip . T.lines $ decUTF8Safe c
 
@@ -1572,7 +1555,7 @@ upgradeGHCup :: ( MonadMask m
                 , MonadUnliftIO m
                 )
              => GHCupDownloads
-             -> Maybe (Path Abs)  -- ^ full file destination to write ghcup into
+             -> Maybe FilePath    -- ^ full file destination to write ghcup into
              -> Bool              -- ^ whether to force update regardless
                                   --   of currently installed version
              -> PlatformRequest
@@ -1592,25 +1575,24 @@ upgradeGHCup dls mtarget force pfreq = do
   when (not force && (latestVer <= pvpToVersion ghcUpVer)) $ throwE NoUpdate
   dli   <- lE $ getDownloadInfo GHCup latestVer pfreq dls
   tmp   <- lift withGHCupTmpDir
-  let fn = [rel|ghcup|]
+  let fn = "ghcup"
   p <- liftE $ download dli tmp (Just fn)
-  let destDir = dirname destFile
+  let destDir = takeFileName destFile
       destFile = fromMaybe (binDir </> fn) mtarget
-  lift $ $(logDebug) [i|mkdir -p #{toFilePath destDir}|]
+  lift $ $(logDebug) [i|mkdir -p #{destDir}|]
   liftIO $ createDirRecursive' destDir
-  lift $ $(logDebug) [i|rm -f #{toFilePath destFile}|]
-  liftIO $ hideError NoSuchThing $ deleteFile destFile
-  lift $ $(logDebug) [i|cp #{toFilePath p} #{toFilePath destFile}|]
+  lift $ $(logDebug) [i|rm -f #{destFile}|]
+  liftIO $ hideError NoSuchThing $ removeFile destFile
+  lift $ $(logDebug) [i|cp #{p} #{destFile}|]
   handleIO (throwE . CopyError . show) $ liftIO $ copyFile p
                                                            destFile
-                                                           Overwrite
   lift $ chmod_755 destFile
 
   liftIO (isInPath destFile) >>= \b -> unless b $
-    lift $ $(logWarn) [i|"#{toFilePath (dirname destFile)}" is not in PATH! You have to add it in order to use ghcup.|]
+    lift $ $(logWarn) [i|"#{takeFileName destFile}" is not in PATH! You have to add it in order to use ghcup.|]
   liftIO (isShadowed destFile) >>= \case
     Nothing -> pure ()
-    Just pa -> lift $ $(logWarn) [i|ghcup is shadowed by "#{toFilePath pa}". The upgrade will not be in effect, unless you remove "#{toFilePath pa}" or make sure "#{toFilePath destDir}" comes before "#{toFilePath (dirname pa)}" in PATH.|]
+    Just pa -> lift $ $(logWarn) [i|ghcup is shadowed by "#{pa}". The upgrade will not be in effect, unless you remove "#{pa}" or make sure "#{destDir}" comes before "#{takeFileName pa}" in PATH.|]
 
   pure latestVer
 
diff --git a/lib/GHCup/Download.hs b/lib/GHCup/Download.hs
index 0b075b0fa2b75e4d2be3266f2162f199a688b432..5234e67ef81dc21a174c16bae32fb779a176b908 100644
--- a/lib/GHCup/Download.hs
+++ b/lib/GHCup/Download.hs
@@ -16,7 +16,7 @@ Copyright   : (c) Julian Ospald, 2020
 License     : LGPL-3.0
 Maintainer  : hasufell@hasufell.de
 Stability   : experimental
-Portability : POSIX
+Portability : portable
 
 Module for handling all download related functions.
 
@@ -57,7 +57,7 @@ import           Data.ByteString                ( ByteString )
 #if defined(INTERNAL_DOWNLOADER)
 import           Data.CaseInsensitive           ( CI )
 #endif
-import           Data.List                      ( find )
+import           Data.List.Extra
 import           Data.Maybe
 import           Data.String.Interpolate
 import           Data.Time.Clock
@@ -66,34 +66,29 @@ import           Data.Time.Clock.POSIX
 import           Data.Time.Format
 #endif
 import           Data.Versions
-import           Data.Word8
 import           GHC.IO.Exception
-import           HPath
-import           HPath.IO                     as HIO hiding ( hideError )
 import           Haskus.Utils.Variant.Excepts
 import           Optics
 import           Prelude                 hiding ( abs
                                                 , readFile
                                                 , writeFile
                                                 )
+import           System.Directory
+import           System.Environment
+import           System.FilePath
 import           System.IO.Error
-import           System.Posix.Env.ByteString    ( getEnv )
 import           URI.ByteString
 
 import qualified Crypto.Hash.SHA256            as SHA256
-import qualified Data.ByteString               as BS
 import qualified Data.ByteString.Base16        as B16
 import qualified Data.ByteString.Lazy          as L
 import qualified Data.Map.Strict               as M
 #if defined(INTERNAL_DOWNLOADER)
 import qualified Data.CaseInsensitive          as CI
-import qualified Data.Text                     as T
 #endif
+import qualified Data.Text                     as T
 import qualified Data.Text.Encoding            as E
 import qualified Data.Yaml                     as Y
-import qualified System.Posix.Files.ByteString as PF
-import qualified System.Posix.RawFilePath.Directory
-                                               as RD
 
 
 
@@ -158,12 +153,12 @@ readFromCache = do
   lift $ $(logWarn)
     [i|Could not get download info, trying cached version (this may not be recent!)|]
   let path = view pathL' ghcupURL
-  yaml_file <- (cacheDir </>) <$> urlBaseName path
+  let yaml_file = cacheDir </> (T.unpack . decUTF8Safe . urlBaseName $ path)
   bs        <-
     handleIO' NoSuchThing
-              (\_ -> throwE $ FileDoesNotExistError (toFilePath yaml_file))
+              (\_ -> throwE $ FileDoesNotExistError yaml_file)
     $ liftIO
-    $ readFile yaml_file
+    $ L.readFile yaml_file
   lE' JSONDecodeError $ first show $ Y.decodeEither' (L.toStrict bs)
 
 
@@ -207,29 +202,27 @@ getBase =
   smartDl uri' = do
     AppState {dirs = Dirs {..}} <- lift ask
     let path = view pathL' uri'
-    json_file <- (cacheDir </>) <$> urlBaseName path
+    let json_file = cacheDir </> (T.unpack . decUTF8Safe . urlBaseName $ path)
     e         <- liftIO $ doesFileExist json_file
     if e
       then do
-        accessTime <-
-          PF.accessTimeHiRes
-            <$> liftIO (PF.getFileStatus (toFilePath json_file))
-        currentTime <- liftIO getPOSIXTime
+        accessTime <- liftIO $ getAccessTime json_file
+        currentTime <- liftIO getCurrentTime
 
         -- access time won't work on most linuxes, but we can try regardless
-        if (currentTime - accessTime) > 300
+        if (utcTimeToPOSIXSeconds currentTime - utcTimeToPOSIXSeconds accessTime) > 300
           then do -- no access in last 5 minutes, re-check upstream mod time
             getModTime >>= \case
               Just modTime -> do
                 fileMod <- liftIO $ getModificationTime json_file
                 if modTime > fileMod
                   then dlWithMod modTime json_file
-                  else liftIO $ readFile json_file
+                  else liftIO $ L.readFile json_file
               Nothing -> do
                 lift $ $(logDebug) [i|Unable to get/parse Last-Modified header|]
                 dlWithoutMod json_file
           else -- access in less than 5 minutes, re-use file
-               liftIO $ readFile json_file
+               liftIO $ L.readFile json_file
       else do
         liftIO $ createDirRecursive' cacheDir
         getModTime >>= \case
@@ -247,9 +240,9 @@ getBase =
       pure bs
     dlWithoutMod json_file = do
       bs <- liftE $ downloadBS uri'
-      liftIO $ hideError doesNotExistErrorType $ deleteFile json_file
-      liftIO $ writeFileL json_file (Just newFilePerms) bs
-      liftIO $ setModificationTime json_file (fromIntegral @Int 0)
+      liftIO $ hideError doesNotExistErrorType $ removeFile json_file
+      liftIO $ L.writeFile json_file bs
+      liftIO $ setModificationTime json_file (posixSecondsToUTCTime (fromIntegral @Int 0))
       pure bs
 
 
@@ -278,11 +271,10 @@ getBase =
 
 #endif
 
-  writeFileWithModTime :: UTCTime -> Path Abs -> L.ByteString -> IO ()
+  writeFileWithModTime :: UTCTime -> FilePath -> L.ByteString -> IO ()
   writeFileWithModTime utctime path content = do
-    let mod_time = utcTimeToPOSIXSeconds utctime
-    writeFileL path (Just newFilePerms) content
-    setModificationTimeHiRes path mod_time
+    L.writeFile path content
+    setModificationTime path utctime
 
 
 getDownloadInfo :: Tool
@@ -334,9 +326,9 @@ download :: ( MonadMask m
             , MonadIO m
             )
          => DownloadInfo
-         -> Path Abs          -- ^ destination dir
-         -> Maybe (Path Rel)  -- ^ optional filename
-         -> Excepts '[DigestError , DownloadFailed] m (Path Abs)
+         -> FilePath          -- ^ destination dir
+         -> Maybe FilePath    -- ^ optional filename
+         -> Excepts '[DigestError , DownloadFailed] m FilePath
 download dli dest mfn
   | scheme == "https" = dl
   | scheme == "http"  = dl
@@ -348,9 +340,9 @@ download dli dest mfn
   cp     = do
     -- destination dir must exist
     liftIO $ createDirRecursive' dest
-    destFile <- getDestFile
-    fromFile <- parseAbs path
-    liftIO $ copyFile fromFile destFile Strict
+    let destFile = getDestFile
+    let fromFile = T.unpack . decUTF8Safe $ path
+    liftIO $ copyFile fromFile destFile
     pure destFile
   dl = do
     let uri' = decUTF8Safe (serializeURIRef' (view dlUri dli))
@@ -358,25 +350,25 @@ download dli dest mfn
 
     -- destination dir must exist
     liftIO $ createDirRecursive' dest
-    destFile <- getDestFile
+    let destFile = getDestFile
 
     -- download
     flip onException
-         (liftIO $ hideError doesNotExistErrorType $ deleteFile destFile)
+         (liftIO $ hideError doesNotExistErrorType $ removeFile destFile)
      $ catchAllE @_ @'[ProcessError, DownloadFailed, UnsupportedScheme]
           (\e ->
-            liftIO (hideError doesNotExistErrorType $ deleteFile destFile)
+            liftIO (hideError doesNotExistErrorType $ removeFile destFile)
               >> (throwE . DownloadFailed $ e)
           ) $ do
               lift getDownloader >>= \case
                 Curl -> do
                   o' <- liftIO getCurlOpts
-                  liftE $ lEM @_ @'[ProcessError] $ liftIO $ exec "curl" True
-                    (o' ++ ["-fL", "-o", toFilePath destFile, serializeURIRef' $ view dlUri dli]) Nothing Nothing
+                  liftE $ lEM @_ @'[ProcessError] $ liftIO $ exec "curl" 
+                    (o' ++ ["-fL", "-o", destFile, (T.unpack . decUTF8Safe) $ serializeURIRef' $ view dlUri dli]) Nothing Nothing
                 Wget -> do
                   o' <- liftIO getWgetOpts
-                  liftE $ lEM @_ @'[ProcessError] $ liftIO $ exec "wget" True
-                    (o' ++ ["-O", toFilePath destFile , serializeURIRef' $ view dlUri dli]) Nothing Nothing
+                  liftE $ lEM @_ @'[ProcessError] $ liftIO $ exec "wget" 
+                    (o' ++ ["-O", destFile , (T.unpack . decUTF8Safe) $ serializeURIRef' $ view dlUri dli]) Nothing Nothing
 #if defined(INTERNAL_DOWNLOADER)
                 Internal -> do
                   (https, host, fullPath, port) <- liftE $ uriToQuadruple (view dlUri dli)
@@ -387,8 +379,8 @@ download dli dest mfn
     pure destFile
 
   -- Manage to find a file we can write the body into.
-  getDestFile :: MonadThrow m => m (Path Abs)
-  getDestFile = maybe (urlBaseName path <&> (dest </>)) (pure . (dest </>)) mfn
+  getDestFile :: FilePath
+  getDestFile = maybe (dest </> T.unpack (decUTF8Safe (urlBaseName path))) (dest </>) mfn
 
   path        = view (dlUri % pathL') dli
 
@@ -404,14 +396,14 @@ downloadCached :: ( MonadMask m
                   , MonadReader AppState m
                   )
                => DownloadInfo
-               -> Maybe (Path Rel)  -- ^ optional filename
-               -> Excepts '[DigestError , DownloadFailed] m (Path Abs)
+               -> Maybe FilePath  -- ^ optional filename
+               -> Excepts '[DigestError , DownloadFailed] m FilePath
 downloadCached dli mfn = do
   cache <- lift getCache
   case cache of
     True -> do
       AppState {dirs = Dirs {..}} <- lift ask
-      fn       <- maybe (urlBaseName $ view (dlUri % pathL') dli) pure mfn
+      let fn = maybe ((T.unpack . decUTF8Safe) $ urlBaseName $ view (dlUri % pathL') dli) id mfn
       let cachfile = cacheDir </> fn
       fileExists <- liftIO $ doesFileExist cachfile
       if
@@ -453,8 +445,8 @@ downloadBS uri'
   | scheme == "http"
   = dl False
   | scheme == "file"
-  = liftIOException doesNotExistErrorType (FileDoesNotExistError path)
-    (liftIO $ RD.readFile path)
+  = liftIOException doesNotExistErrorType (FileDoesNotExistError $ T.unpack $ decUTF8Safe $ path)
+    (liftIO $ L.readFile (T.unpack $ decUTF8Safe path))
   | otherwise
   = throwE UnsupportedScheme
 
@@ -470,20 +462,20 @@ downloadBS uri'
     lift getDownloader >>= \case
       Curl -> do
         o' <- liftIO getCurlOpts
-        let exe = [rel|curl|]
-            args = o' ++ ["-sSfL", serializeURIRef' uri']
+        let exe = "curl"
+            args = o' ++ ["-sSfL", T.unpack $ decUTF8Safe $ serializeURIRef' uri']
         liftIO (executeOut exe args Nothing) >>= \case
           CapturedProcess ExitSuccess stdout _ -> do
-            pure $ L.fromStrict stdout
-          CapturedProcess (ExitFailure i') _ _ -> throwE $ NonZeroExit i' (toFilePath exe) args
+            pure stdout
+          CapturedProcess (ExitFailure i') _ _ -> throwE $ NonZeroExit i' exe args
       Wget -> do
         o' <- liftIO getWgetOpts
-        let exe = [rel|wget|]
-            args = o' ++ ["-qO-", serializeURIRef' uri']
+        let exe = "wget"
+            args = o' ++ ["-qO-", T.unpack $ decUTF8Safe $ serializeURIRef' uri']
         liftIO (executeOut exe args Nothing) >>= \case
           CapturedProcess ExitSuccess stdout _ -> do
-            pure $ L.fromStrict stdout
-          CapturedProcess (ExitFailure i') _ _ -> throwE $ NonZeroExit i' (toFilePath exe) args
+            pure stdout
+          CapturedProcess (ExitFailure i') _ _ -> throwE $ NonZeroExit i' exe args
 #if defined(INTERNAL_DOWNLOADER)
       Internal -> do
         (_, host', fullPath', port') <- liftE $ uriToQuadruple uri'
@@ -493,31 +485,31 @@ downloadBS uri'
 
 checkDigest :: (MonadIO m, MonadThrow m, MonadLogger m, MonadReader AppState m)
             => DownloadInfo
-            -> Path Abs
+            -> FilePath
             -> Excepts '[DigestError] m ()
 checkDigest dli file = do
   verify <- lift ask <&> (not . noVerify . settings)
   when verify $ do
-    p' <- toFilePath <$> basename file
+    let p' = takeFileName file
     lift $ $(logInfo) [i|verifying digest of: #{p'}|]
-    c <- liftIO $ readFile file
+    c <- liftIO $ L.readFile file
     cDigest <- throwEither . E.decodeUtf8' . B16.encode . SHA256.hashlazy $ c
     let eDigest = view dlHash dli
     when ((cDigest /= eDigest) && verify) $ throwE (DigestError cDigest eDigest)
 
 
 -- | Get additional curl args from env. This is an undocumented option.
-getCurlOpts :: IO [ByteString]
+getCurlOpts :: IO [String]
 getCurlOpts =
-  getEnv "GHCUP_CURL_OPTS" >>= \case
-    Just r  -> pure $ BS.split _space r
+  lookupEnv "GHCUP_CURL_OPTS" >>= \case
+    Just r  -> pure $ splitOn " " r
     Nothing -> pure []
 
 
 -- | Get additional wget args from env. This is an undocumented option.
-getWgetOpts :: IO [ByteString]
+getWgetOpts :: IO [String]
 getWgetOpts =
-  getEnv "GHCUP_WGET_OPTS" >>= \case
-    Just r  -> pure $ BS.split _space r
+  lookupEnv "GHCUP_WGET_OPTS" >>= \case
+    Just r  -> pure $ splitOn " " r
     Nothing -> pure []
 
diff --git a/lib/GHCup/Download/IOStreams.hs b/lib/GHCup/Download/IOStreams.hs
index d463fcc178fca06565f3ce898e34d111d56c07d7..8eb94e413909b8b6e8c05933ea7cb535b96e90db 100644
--- a/lib/GHCup/Download/IOStreams.hs
+++ b/lib/GHCup/Download/IOStreams.hs
@@ -24,8 +24,6 @@ import           Data.CaseInsensitive           ( CI )
 import           Data.IORef
 import           Data.Maybe
 import           Data.Text.Read
-import           HPath
-import           HPath.IO                      as HIO
 import           Haskus.Utils.Variant.Excepts
 import           Network.Http.Client     hiding ( URL )
 import           Optics
@@ -33,11 +31,8 @@ import           Prelude                 hiding ( abs
                                                 , readFile
                                                 , writeFile
                                                 )
-import "unix"    System.Posix.IO.ByteString
-                                         hiding ( fdWrite )
-import "unix-bytestring" System.Posix.IO.ByteString
-                                                ( fdWrite )
 import           System.ProgressBar
+import           System.IO
 import           URI.ByteString
 
 import qualified Data.ByteString               as BS
@@ -81,12 +76,12 @@ downloadToFile :: (MonadMask m, MonadIO m)
                -> ByteString       -- ^ host (e.g. "www.example.com")
                -> ByteString       -- ^ path (e.g. "/my/file") including query
                -> Maybe Int        -- ^ optional port (e.g. 3000)
-               -> Path Abs         -- ^ destination file to create and write to
+               -> FilePath         -- ^ destination file to create and write to
                -> Excepts '[DownloadFailed] m ()
 downloadToFile https host fullPath port destFile = do
-  fd <- liftIO $ createRegularFileFd newFilePerms destFile
-  let stepper = fdWrite fd
-  flip finally (liftIO $ closeFd fd)
+  fd <- liftIO $ openFile destFile WriteMode
+  let stepper = BS.hPut fd
+  flip finally (liftIO $ hClose fd)
     $ reThrowAll DownloadFailed $ downloadInternal True https host fullPath port stepper
 
 
diff --git a/lib/GHCup/Errors.hs b/lib/GHCup/Errors.hs
index f86d6909cb131152a534261d14edc1ec3995fe2a..de9576cb3118e7b88a91ed422598c43e772dcf6d 100644
--- a/lib/GHCup/Errors.hs
+++ b/lib/GHCup/Errors.hs
@@ -15,12 +15,11 @@ Copyright   : (c) Julian Ospald, 2020
 License     : LGPL-3.0
 Maintainer  : hasufell@hasufell.de
 Stability   : experimental
-Portability : POSIX
+Portability : portable
 -}
 module GHCup.Errors where
 
 import           GHCup.Types
-import           GHCup.Utils.Prelude
 
 #if !defined(TAR)
 import           Codec.Archive
@@ -28,11 +27,9 @@ import           Codec.Archive
 import qualified Codec.Archive.Tar             as Tar
 #endif
 import           Control.Exception.Safe
-import           Data.ByteString                ( ByteString )
 import           Data.String.Interpolate
 import           Data.Text                      ( Text )
 import           Data.Versions
-import           HPath
 import           Haskus.Utils.Variant
 import           Text.PrettyPrint
 import           Text.PrettyPrint.HughesPJClass
@@ -86,12 +83,12 @@ instance Pretty DistroNotFound where
     text "Unable to figure out the distribution of the host."
 
 -- | The archive format is unknown. We don't know how to extract it.
-data UnknownArchive = UnknownArchive ByteString
+data UnknownArchive = UnknownArchive FilePath
   deriving Show
 
 instance Pretty UnknownArchive where
   pPrint (UnknownArchive file) =
-    text [i|The archive format is unknown. We don't know how to extract the file "#{decUTF8Safe file}"|]
+    text [i|The archive format is unknown. We don't know how to extract the file "#{file}"|]
 
 -- | The scheme is not supported (such as ftp).
 data UnsupportedScheme = UnsupportedScheme
@@ -143,12 +140,12 @@ instance Pretty NotInstalled where
     text [i|The version "#{prettyShow ver}" of the tool "#{tool}" is not installed.|]
 
 -- | An executable was expected to be in PATH, but was not found.
-data NotFoundInPATH = NotFoundInPATH (Path Rel)
+data NotFoundInPATH = NotFoundInPATH FilePath
   deriving Show
 
 instance Pretty NotFoundInPATH where
   pPrint (NotFoundInPATH exe) =
-    text [i|The exe "#{decUTF8Safe . toFilePath $ exe}" was not found in PATH.|]
+    text [i|The exe "#{exe}" was not found in PATH.|]
 
 -- | JSON decoding failed.
 data JSONError = JSONDecodeError String
@@ -160,12 +157,12 @@ instance Pretty JSONError where
 
 -- | A file that is supposed to exist does not exist
 -- (e.g. when we use file scheme to "download" something).
-data FileDoesNotExistError = FileDoesNotExistError ByteString
+data FileDoesNotExistError = FileDoesNotExistError FilePath
   deriving Show
 
 instance Pretty FileDoesNotExistError where
   pPrint (FileDoesNotExistError file) =
-    text [i|File "#{decUTF8Safe file}" does not exist.|]
+    text [i|File "#{file}" does not exist.|]
 
 data TarDirDoesNotExist = TarDirDoesNotExist TarDir
   deriving Show
@@ -252,11 +249,11 @@ deriving instance Show DownloadFailed
 
 
 -- | A build failed.
-data BuildFailed = forall es . Show (V es) => BuildFailed (Path Abs) (V es)
+data BuildFailed = forall es . Show (V es) => BuildFailed FilePath (V es)
 
 instance Pretty BuildFailed where
   pPrint (BuildFailed path reason) =
-    text [i|BuildFailed failed in dir "#{decUTF8Safe . toFilePath $ path}": #{reason}|]
+    text [i|BuildFailed failed in dir "#{path}": #{reason}|]
 
 deriving instance Show BuildFailed
 
diff --git a/lib/GHCup/Platform.hs b/lib/GHCup/Platform.hs
index e05ba331a6057180dc68c58b4ae0cfbfe6a68ff4..e36cb5007aec4c6c3923c25a9d6bbba827e810aa 100644
--- a/lib/GHCup/Platform.hs
+++ b/lib/GHCup/Platform.hs
@@ -13,7 +13,7 @@ Copyright   : (c) Julian Ospald, 2020
 License     : LGPL-3.0
 Maintainer  : hasufell@hasufell.de
 Stability   : experimental
-Portability : POSIX
+Portability : portable
 -}
 module GHCup.Platform where
 
@@ -36,18 +36,20 @@ import           Data.Maybe
 import           Data.String.Interpolate
 import           Data.Text                      ( Text )
 import           Data.Versions
-import           HPath
-import           HPath.IO
 import           Haskus.Utils.Variant.Excepts
 import           Prelude                 hiding ( abs
                                                 , readFile
                                                 , writeFile
                                                 )
 import           System.Info
+import           System.Directory
 import           System.OsRelease
 import           Text.Regex.Posix
 
 import qualified Data.Text                     as T
+import qualified Data.Text.IO                  as T
+
+
 
     --------------------------
     --[ Platform detection ]--
@@ -96,22 +98,23 @@ getPlatform = do
           . versioning
           -- TODO: maybe do this somewhere else
           . getMajorVersion
-          . decUTF8Safe
+          . decUTF8Safe'
         <$> getDarwinVersion
       pure $ PlatformResult { _platform = Darwin, _distroVersion = ver }
     "freebsd" -> do
       ver <-
-        either (const Nothing) Just . versioning . decUTF8Safe
+        either (const Nothing) Just . versioning . decUTF8Safe'
           <$> getFreeBSDVersion
       pure $ PlatformResult { _platform = FreeBSD, _distroVersion = ver }
+    "mingw32" -> pure PlatformResult { _platform = Windows, _distroVersion = Nothing }
     what -> throwE $ NoCompatiblePlatform what
   lift $ $(logDebug) [i|Identified Platform as: #{pfr}|]
   pure pfr
  where
   getMajorVersion = T.intercalate "." . take 2 . T.split (== '.')
   getFreeBSDVersion =
-    liftIO $ fmap _stdOut $ executeOut [rel|freebsd-version|] [] Nothing
-  getDarwinVersion = liftIO $ fmap _stdOut $ executeOut [rel|sw_vers|]
+    liftIO $ fmap _stdOut $ executeOut "freebsd-version" [] Nothing
+  getDarwinVersion = liftIO $ fmap _stdOut $ executeOut "sw_vers"
                                                         ["-productVersion"]
                                                         Nothing
 
@@ -147,12 +150,12 @@ getLinuxDistro = do
    where
     regex x = makeRegexOpts compIgnoreCase execBlank ([s|\<|] ++ x ++ [s|\>|])
 
-  lsb_release_cmd :: Path Rel
-  lsb_release_cmd = [rel|lsb-release|]
-  redhat_release :: Path Abs
-  redhat_release = [abs|/etc/redhat-release|]
-  debian_version :: Path Abs
-  debian_version = [abs|/etc/debian_version|]
+  lsb_release_cmd :: FilePath
+  lsb_release_cmd = "lsb-release"
+  redhat_release :: FilePath
+  redhat_release = "/etc/redhat-release"
+  debian_version :: FilePath
+  debian_version = "/etc/debian_version"
 
   try_os_release :: IO (Text, Maybe Text)
   try_os_release = do
@@ -165,11 +168,11 @@ getLinuxDistro = do
     (Just _) <- findExecutable lsb_release_cmd
     name     <- fmap _stdOut $ executeOut lsb_release_cmd ["-si"] Nothing
     ver      <- fmap _stdOut $ executeOut lsb_release_cmd ["-sr"] Nothing
-    pure (decUTF8Safe name, Just $ decUTF8Safe ver)
+    pure (decUTF8Safe' name, Just $ decUTF8Safe' ver)
 
   try_redhat_release :: IO (Text, Maybe Text)
   try_redhat_release = do
-    t <- fmap decUTF8Safe' $ readFile redhat_release
+    t <- T.readFile redhat_release
     let nameRegex n =
           makeRegexOpts compIgnoreCase
                         execBlank
@@ -191,5 +194,5 @@ getLinuxDistro = do
 
   try_debian_version :: IO (Text, Maybe Text)
   try_debian_version = do
-    ver <- readFile debian_version
-    pure (T.pack "debian", Just . decUTF8Safe' $ ver)
+    ver <- T.readFile debian_version
+    pure (T.pack "debian", Just ver)
diff --git a/lib/GHCup/Requirements.hs b/lib/GHCup/Requirements.hs
index a1b4a844f0efe2a7d7eeb7a503f8215ea9cf78f5..31a9ab40b73f5e49d344d84ab485397315c19243 100644
--- a/lib/GHCup/Requirements.hs
+++ b/lib/GHCup/Requirements.hs
@@ -7,7 +7,7 @@ Copyright   : (c) Julian Ospald, 2020
 License     : LGPL-3.0
 Maintainer  : hasufell@hasufell.de
 Stability   : experimental
-Portability : POSIX
+Portability : portable
 -}
 module GHCup.Requirements where
 
diff --git a/lib/GHCup/Types.hs b/lib/GHCup/Types.hs
index 4e1ae2cc7b8fb47f5bd989cac9d62c22ca2361cf..3bd8de98a6feee6dab1812d00ab7629d40ef24ef 100644
--- a/lib/GHCup/Types.hs
+++ b/lib/GHCup/Types.hs
@@ -11,26 +11,39 @@ Copyright   : (c) Julian Ospald, 2020
 License     : LGPL-3.0
 Maintainer  : hasufell@hasufell.de
 Stability   : experimental
-Portability : POSIX
+Portability : portable
 -}
-module GHCup.Types where
+module GHCup.Types
+  ( module GHCup.Types
+#if defined(BRICK)
+  , module Graphics.Vty (Key(..))
+#endif
+  )
+  where
 
 import           Data.Map.Strict                ( Map )
 import           Data.List.NonEmpty             ( NonEmpty (..) )
-import           Data.String.Interpolate
 import           Data.Text                      ( Text )
 import           Data.Versions
-import           HPath
 import           Text.PrettyPrint.HughesPJClass (Pretty, pPrint, text)
 import           URI.ByteString
+#if defined(BRICK)
+import           Graphics.Vty                   ( Key(..) )
+#endif
 
 import qualified Data.Text                     as T
-import qualified Data.Text.Encoding            as E
-import qualified Data.Text.Encoding.Error      as E
 import qualified GHC.Generics                  as GHC
-import qualified Graphics.Vty                  as Vty
 
 
+#if !defined(BRICK)
+data Key = KEsc  | KChar Char | KBS | KEnter
+         | KLeft | KRight | KUp | KDown
+         | KUpLeft | KUpRight | KDownLeft | KDownRight | KCenter
+         | KFun Int | KBackTab | KPrtScr | KPause | KIns
+         | KHome | KPageUp | KDel | KEnd | KPageDown | KBegin | KMenu
+    deriving (Eq,Show,Read,Ord,GHC.Generic)
+#endif
+
 
     --------------------
     --[ GHCInfo Tree ]--
@@ -157,12 +170,15 @@ data Platform = Linux LinuxDistro
               | Darwin
               -- ^ must exit
               | FreeBSD
+              | Windows
+              -- ^ must exit
   deriving (Eq, GHC.Generic, Ord, Show)
 
 platformToString :: Platform -> String
 platformToString (Linux distro) = "linux-" ++ distroToString distro
 platformToString Darwin = "darwin"
 platformToString FreeBSD = "freebsd"
+platformToString Windows = "windows"
 
 instance Pretty Platform where
   pPrint = text . platformToString
@@ -218,12 +234,12 @@ data DownloadInfo = DownloadInfo
 
 
 -- | How to descend into a tar archive.
-data TarDir = RealDir (Path Rel)
+data TarDir = RealDir FilePath
             | RegexDir String     -- ^ will be compiled to regex, the first match will "win"
             deriving (Eq, Ord, GHC.Generic, Show)
 
 instance Pretty TarDir where
-  pPrint (RealDir path) = text [i|#{E.decodeUtf8With E.lenientDecode . toFilePath $ path}|]
+  pPrint (RealDir path) = text path
   pPrint (RegexDir regex) = text regex
 
 
@@ -250,42 +266,42 @@ defaultUserSettings :: UserSettings
 defaultUserSettings = UserSettings Nothing Nothing Nothing Nothing Nothing Nothing Nothing
 
 data UserKeyBindings = UserKeyBindings
-  { kUp        :: Maybe Vty.Key
-  , kDown      :: Maybe Vty.Key
-  , kQuit      :: Maybe Vty.Key
-  , kInstall   :: Maybe Vty.Key
-  , kUninstall :: Maybe Vty.Key
-  , kSet       :: Maybe Vty.Key
-  , kChangelog :: Maybe Vty.Key
-  , kShowAll   :: Maybe Vty.Key
-  , kShowAllTools :: Maybe Vty.Key
+  { kUp        :: Maybe Key
+  , kDown      :: Maybe Key
+  , kQuit      :: Maybe Key
+  , kInstall   :: Maybe Key
+  , kUninstall :: Maybe Key
+  , kSet       :: Maybe Key
+  , kChangelog :: Maybe Key
+  , kShowAll   :: Maybe Key
+  , kShowAllTools :: Maybe Key
   }
   deriving (Show, GHC.Generic)
 
 data KeyBindings = KeyBindings
-  { bUp        :: Vty.Key
-  , bDown      :: Vty.Key
-  , bQuit      :: Vty.Key
-  , bInstall   :: Vty.Key
-  , bUninstall :: Vty.Key
-  , bSet       :: Vty.Key
-  , bChangelog :: Vty.Key
-  , bShowAllVersions :: Vty.Key
-  , bShowAllTools :: Vty.Key
+  { bUp        :: Key
+  , bDown      :: Key
+  , bQuit      :: Key
+  , bInstall   :: Key
+  , bUninstall :: Key
+  , bSet       :: Key
+  , bChangelog :: Key
+  , bShowAllVersions :: Key
+  , bShowAllTools :: Key
   }
   deriving (Show, GHC.Generic)
 
 defaultKeyBindings :: KeyBindings
 defaultKeyBindings = KeyBindings
-  { bUp = Vty.KUp
-  , bDown = Vty.KDown
-  , bQuit = Vty.KChar 'q'
-  , bInstall = Vty.KChar 'i'
-  , bUninstall = Vty.KChar 'u'
-  , bSet = Vty.KChar 's'
-  , bChangelog = Vty.KChar 'c'
-  , bShowAllVersions = Vty.KChar 'a'
-  , bShowAllTools = Vty.KChar 't'
+  { bUp = KUp
+  , bDown = KDown
+  , bQuit = KChar 'q'
+  , bInstall = KChar 'i'
+  , bUninstall = KChar 'u'
+  , bSet = KChar 's'
+  , bChangelog = KChar 'c'
+  , bShowAllVersions = KChar 'a'
+  , bShowAllTools = KChar 't'
   }
 
 data AppState = AppState
@@ -305,11 +321,11 @@ data Settings = Settings
   deriving (Show, GHC.Generic)
 
 data Dirs = Dirs
-  { baseDir  :: Path Abs
-  , binDir   :: Path Abs
-  , cacheDir :: Path Abs
-  , logsDir  :: Path Abs
-  , confDir  :: Path Abs
+  { baseDir  :: FilePath
+  , binDir   :: FilePath
+  , cacheDir :: FilePath
+  , logsDir  :: FilePath
+  , confDir  :: FilePath
   }
   deriving Show
 
@@ -326,10 +342,10 @@ data Downloader = Curl
   deriving (Eq, Show, Ord)
 
 data DebugInfo = DebugInfo
-  { diBaseDir  :: Path Abs
-  , diBinDir   :: Path Abs
-  , diGHCDir   :: Path Abs
-  , diCacheDir :: Path Abs
+  { diBaseDir  :: FilePath
+  , diBinDir   :: FilePath
+  , diGHCDir   :: FilePath
+  , diCacheDir :: FilePath
   , diArch     :: Architecture
   , diPlatform :: PlatformResult
   }
diff --git a/lib/GHCup/Types/JSON.hs b/lib/GHCup/Types/JSON.hs
index 390a61b978017e37eb540cdebde0d25e1044b199..39501c9f5be86ebe917cf0e179e459dbe69cc17a 100644
--- a/lib/GHCup/Types/JSON.hs
+++ b/lib/GHCup/Types/JSON.hs
@@ -17,7 +17,7 @@ Copyright   : (c) Julian Ospald, 2020
 License     : LGPL-3.0
 Maintainer  : hasufell@hasufell.de
 Stability   : experimental
-Portability : POSIX
+Portability : portable
 -}
 module GHCup.Types.JSON where
 
@@ -33,15 +33,11 @@ import           Data.List.NonEmpty             ( NonEmpty(..) )
 import           Data.Text.Encoding            as E
 import           Data.Versions
 import           Data.Void
-import           Data.Word8
-import           HPath
 import           URI.ByteString
 import           Text.Casing
 
-import qualified Data.ByteString               as BS
 import qualified Data.List.NonEmpty            as NE
 import qualified Data.Text                     as T
-import qualified Graphics.Vty                  as Vty
 import qualified Text.Megaparsec               as MP
 import qualified Text.Megaparsec.Char          as MPC
 
@@ -64,7 +60,7 @@ deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''Downlo
 deriveJSON defaultOptions { sumEncoding = ObjectWithSingleField } ''URLSource
 deriveJSON defaultOptions { fieldLabelModifier = \str' -> maybe str' T.unpack . T.stripPrefix (T.pack "u-") . T.pack . kebab $ str' } ''UserSettings
 deriveJSON defaultOptions { fieldLabelModifier = \str' -> maybe str' T.unpack . T.stripPrefix (T.pack "k-") . T.pack . kebab $ str' } ''UserKeyBindings
-deriveJSON defaultOptions { sumEncoding = ObjectWithSingleField } ''Vty.Key
+deriveJSON defaultOptions { sumEncoding = ObjectWithSingleField } ''Key
 
 instance ToJSON Tag where
   toJSON Latest             = String "Latest"
@@ -128,11 +124,13 @@ instance ToJSONKey Platform where
     Darwin  -> T.pack "Darwin"
     FreeBSD -> T.pack "FreeBSD"
     Linux d -> T.pack ("Linux_" <> show d)
+    Windows -> T.pack "Windows"
 
 instance FromJSONKey Platform where
   fromJSONKey = FromJSONKeyTextParser $ \t -> if
     | T.pack "Darwin" == t -> pure Darwin
     | T.pack "FreeBSD" == t -> pure FreeBSD
+    | T.pack "Windows" == t -> pure Windows
     | T.pack "Linux_" `T.isPrefixOf` t -> case
         T.stripPrefix (T.pack "Linux_") t
       of
@@ -199,20 +197,6 @@ instance ToJSONKey Tool where
 instance FromJSONKey Tool where
   fromJSONKey = genericFromJSONKey defaultJSONKeyOptions
 
-instance ToJSON (Path Rel) where
-  toJSON p = case and . fmap isAscii . BS.unpack $ fp of
-    True  -> toJSON . decUTF8Safe $ fp
-    False -> String "/not/a/valid/path"
-    where fp = toFilePath p
-
-instance FromJSON (Path Rel) where
-  parseJSON = withText "HPath Rel" $ \t -> do
-    let d = encodeUtf8 t
-    case parseRel d of
-      Right x -> pure x
-      Left  e -> fail $ "Failure in HPath Rel (FromJSON)" <> show e
-
-
 instance ToJSON TarDir where
   toJSON (RealDir  p) = toJSON p
   toJSON (RegexDir r) = object ["RegexDir" .= r]
diff --git a/lib/GHCup/Types/Optics.hs b/lib/GHCup/Types/Optics.hs
index 2486175c281d1c4edfd1d085f50f7e999a63792f..d971ccd78ed38a4ba720638e64a6a1adf4e45d5f 100644
--- a/lib/GHCup/Types/Optics.hs
+++ b/lib/GHCup/Types/Optics.hs
@@ -7,7 +7,7 @@ Copyright   : (c) Julian Ospald, 2020
 License     : LGPL-3.0
 Maintainer  : hasufell@hasufell.de
 Stability   : experimental
-Portability : POSIX
+Portability : portable
 -}
 module GHCup.Types.Optics where
 
diff --git a/lib/GHCup/Utils.hs b/lib/GHCup/Utils.hs
index 742e12110d92daea8518f77e804afd941365da02..7679e4f0326ac58d70b006d3a7ec486d73ecbcfd 100644
--- a/lib/GHCup/Utils.hs
+++ b/lib/GHCup/Utils.hs
@@ -14,7 +14,7 @@ Copyright   : (c) Julian Ospald, 2020
 License     : LGPL-3.0
 Maintainer  : hasufell@hasufell.de
 Stability   : experimental
-Portability : POSIX
+Portability : portable
 
 This module contains GHCup helpers specific to
 installation and introspection of files/versions etc.
@@ -59,20 +59,12 @@ import           Data.Text                      ( Text )
 import           Data.Versions
 import           Data.Word8
 import           GHC.IO.Exception
-import           HPath
-import           HPath.IO                hiding ( hideError )
 import           Haskus.Utils.Variant.Excepts
 import           Optics
-import           Prelude                 hiding ( abs
-                                                , readFile
-                                                , writeFile
-                                                )
 import           Safe
+import           System.Directory      hiding   ( findFiles )
+import           System.FilePath
 import           System.IO.Error
-import           System.Posix.FilePath          ( getSearchPath
-                                                , takeFileName
-                                                )
-import           System.Posix.Files.ByteString  ( readSymbolicLink )
 import           Text.Regex.Posix
 import           URI.ByteString
 
@@ -85,10 +77,7 @@ import qualified Codec.Compression.Lzma        as Lzma
 import qualified Data.ByteString               as B
 import qualified Data.ByteString.Lazy          as BL
 import qualified Data.Map.Strict               as Map
-#if !defined(TAR)
 import qualified Data.Text                     as T
-#endif
-import qualified Data.Text.Encoding            as E
 import qualified Text.Megaparsec               as MP
 
 
@@ -102,14 +91,13 @@ import qualified Text.Megaparsec               as MP
 
 -- | The symlink destination of a ghc tool.
 ghcLinkDestination :: (MonadReader AppState m, MonadThrow m, MonadIO m)
-                   => ByteString -- ^ the tool, such as 'ghc', 'haddock' etc.
+                   => FilePath -- ^ the tool, such as 'ghc', 'haddock' etc.
                    -> GHCTargetVersion
-                   -> m ByteString
+                   -> m FilePath
 ghcLinkDestination tool ver = do
   AppState { dirs = Dirs {..} } <- ask
-  t <- parseRel tool
   ghcd <- ghcupGHCDir ver
-  pure (relativeSymlink binDir (ghcd </> [rel|bin|] </> t))
+  pure (relativeSymlink binDir (ghcd </> "bin" </> tool))
 
 
 -- | Removes the minor GHC symlinks, e.g. ghc-8.6.5.
@@ -127,10 +115,10 @@ rmMinorSymlinks tv@GHCTargetVersion{..} = do
 
   files                         <- liftE $ ghcToolFiles tv
   forM_ files $ \f -> do
-    f_xyz <- liftIO $ parseRel (toFilePath f <> B.singleton _hyphen <> verToBS _tvVersion)
+    let f_xyz = f <> "-" <> T.unpack (prettyVer _tvVersion)
     let fullF = binDir </> f_xyz
-    lift $ $(logDebug) [i|rm -f #{toFilePath fullF}|]
-    liftIO $ hideError doesNotExistErrorType $ deleteFile fullF
+    lift $ $(logDebug) [i|rm -f #{fullF}|]
+    liftIO $ hideError doesNotExistErrorType $ removeFile fullF
 
 
 -- | Removes the set ghc version for the given target, if any.
@@ -149,12 +137,12 @@ rmPlain target = do
     files <- liftE $ ghcToolFiles tv
     forM_ files $ \f -> do
       let fullF = binDir </> f
-      lift $ $(logDebug) [i|rm -f #{toFilePath fullF}|]
-      liftIO $ hideError doesNotExistErrorType $ deleteFile fullF
+      lift $ $(logDebug) [i|rm -f #{fullF}|]
+      liftIO $ hideError doesNotExistErrorType $ removeFile fullF
     -- old ghcup
-    let hdc_file = binDir </> [rel|haddock-ghc|]
-    lift $ $(logDebug) [i|rm -f #{toFilePath hdc_file}|]
-    liftIO $ hideError doesNotExistErrorType $ deleteFile hdc_file
+    let hdc_file = binDir </> "haddock-ghc"
+    lift $ $(logDebug) [i|rm -f #{hdc_file}|]
+    liftIO $ hideError doesNotExistErrorType $ removeFile hdc_file
 
 
 -- | Remove the major GHC symlink, e.g. ghc-8.6.
@@ -174,10 +162,10 @@ rmMajorSymlinks tv@GHCTargetVersion{..} = do
 
   files                         <- liftE $ ghcToolFiles tv
   forM_ files $ \f -> do
-    f_xyz <- liftIO $ parseRel (toFilePath f <> B.singleton _hyphen <> E.encodeUtf8 v')
+    let f_xyz = f <> "-" <> T.unpack v'
     let fullF = binDir </> f_xyz
-    lift $ $(logDebug) [i|rm -f #{toFilePath fullF}|]
-    liftIO $ hideError doesNotExistErrorType $ deleteFile fullF
+    lift $ $(logDebug) [i|rm -f #{fullF}|]
+    liftIO $ hideError doesNotExistErrorType $ removeFile fullF
 
 
 
@@ -208,19 +196,17 @@ ghcSet :: (MonadReader AppState m, MonadThrow m, MonadIO m)
        -> m (Maybe GHCTargetVersion)
 ghcSet mtarget = do
   AppState {dirs = Dirs {..}} <- ask
-  ghc    <- parseRel $ E.encodeUtf8 (maybe "ghc" (<> "-ghc") mtarget)
+  let ghc = maybe "ghc" (\t -> T.unpack t <> "-ghc") mtarget
   let ghcBin = binDir </> ghc
 
   -- link destination is of the form ../ghc/<ver>/bin/ghc
   -- for old ghcup, it is ../ghc/<ver>/bin/ghc-<ver>
   liftIO $ handleIO' NoSuchThing (\_ -> pure Nothing) $ do
-    link <- readSymbolicLink $ toFilePath ghcBin
+    link <- liftIO $ getSymbolicLinkTarget ghcBin
     Just <$> ghcLinkVersion link
 
-ghcLinkVersion :: MonadThrow m => ByteString -> m GHCTargetVersion
-ghcLinkVersion bs = do
-  t <- throwEither $ E.decodeUtf8' bs
-  throwEither $ MP.parse parser "ghcLinkVersion" t
+ghcLinkVersion :: MonadThrow m => FilePath -> m GHCTargetVersion
+ghcLinkVersion (T.pack -> t) = throwEither $ MP.parse parser "ghcLinkVersion" t
  where
   parser =
       (do
@@ -240,10 +226,10 @@ ghcLinkVersion bs = do
 
 -- | Get all installed GHCs by reading ~/.ghcup/ghc/<dir>.
 -- If a dir cannot be parsed, returns left.
-getInstalledGHCs :: (MonadReader AppState m, MonadIO m) => m [Either (Path Rel) GHCTargetVersion]
+getInstalledGHCs :: (MonadReader AppState m, MonadIO m) => m [Either FilePath GHCTargetVersion]
 getInstalledGHCs = do
   ghcdir <- ghcupGHCBaseDir
-  fs     <- liftIO $ hideErrorDef [NoSuchThing] [] $ getDirsFiles' ghcdir
+  fs     <- liftIO $ hideErrorDef [NoSuchThing] [] $ listDirectory ghcdir
   forM fs $ \f -> case parseGHCupGHCDir f of
     Right r -> pure $ Right r
     Left  _ -> pure $ Left f
@@ -251,7 +237,7 @@ getInstalledGHCs = do
 
 -- | Get all installed cabals, by matching on @~\/.ghcup\/bin/cabal-*@.
 getInstalledCabals :: (MonadLogger m, MonadReader AppState m, MonadIO m, MonadCatch m)
-                   => m [Either (Path Rel) Version]
+                   => m [Either FilePath Version]
 getInstalledCabals = do
   cs <- cabalSet -- for legacy cabal
   getInstalledCabals' cs
@@ -259,13 +245,13 @@ getInstalledCabals = do
 
 getInstalledCabals' :: (MonadLogger m, MonadReader AppState m, MonadIO m, MonadCatch m)
                    => Maybe Version
-                   -> m [Either (Path Rel) Version]
+                   -> m [Either FilePath Version]
 getInstalledCabals' cs = do
   AppState {dirs = Dirs {..}} <- ask
   bins   <- liftIO $ handleIO (\_ -> pure []) $ findFiles
     binDir
     (makeRegexOpts compExtended execBlank ([s|^cabal-.*$|] :: ByteString))
-  vs <- forM bins $ \f -> case fmap (version . decUTF8Safe) . B.stripPrefix "cabal-" . toFilePath $ f of
+  vs <- forM bins $ \f -> case fmap (version . T.pack) . stripPrefix "cabal-" $ f of
     Just (Right r) -> pure $ Right r
     Just (Left  _) -> pure $ Left f
     Nothing        -> pure $ Left f
@@ -283,8 +269,8 @@ cabalInstalled ver = do
 cabalSet :: (MonadLogger m, MonadReader AppState m, MonadIO m, MonadThrow m, MonadCatch m) => m (Maybe Version)
 cabalSet = do
   AppState {dirs = Dirs {..}} <- ask
-  let cabalbin = binDir </> [rel|cabal|]
-  b        <- handleIO (\_ -> pure False) $ fmap (== SymbolicLink) $ liftIO $ getFileType cabalbin
+  let cabalbin = binDir </> "cabal"
+  b        <- handleIO (\_ -> pure False) $ liftIO $ pathIsSymbolicLink cabalbin
   if
     | b -> do
       handleIO' NoSuchThing (\_ -> pure Nothing) $ do
@@ -294,11 +280,11 @@ cabalSet = do
             $(logWarn) [i|Symlink #{cabalbin} is broken.|]
             pure Nothing
           else do
-            link <- liftIO $ readSymbolicLink $ toFilePath cabalbin
+            link <- liftIO $ getSymbolicLinkTarget cabalbin
             case linkVersion link of
               Right v -> pure $ Just v
               Left err -> do
-                $(logWarn) [i|Failed to parse cabal symlink target with: "#{err}". The symlink #{toFilePath cabalbin} needs to point to valid cabal binary, such as 'cabal-3.4.0.0'.|]
+                $(logWarn) [i|Failed to parse cabal symlink target with: "#{err}". The symlink #{cabalbin} needs to point to valid cabal binary, such as 'cabal-3.4.0.0'.|]
                 pure Nothing
     | otherwise -> do -- legacy behavior
       mc <- liftIO $ handleIO (\_ -> pure Nothing) $ fmap Just $ executeOut
@@ -306,8 +292,8 @@ cabalSet = do
         ["--numeric-version"]
         Nothing
       fmap join $ forM mc $ \c -> if
-        | not (B.null (_stdOut c)), _exitCode c == ExitSuccess -> do
-          let reportedVer = fst . B.spanEnd (== _lf) . _stdOut $ c
+        | not (BL.null (_stdOut c)), _exitCode c == ExitSuccess -> do
+          let reportedVer = fst . B.spanEnd (== _lf) . BL.toStrict . _stdOut $ c
           case version $ decUTF8Safe reportedVer of
             Left  e -> throwM e
             Right r -> pure $ Just r
@@ -316,10 +302,8 @@ cabalSet = do
   -- We try to be extra permissive with link destination parsing,
   -- because of:
   --   https://gitlab.haskell.org/haskell/ghcup-hs/-/issues/119
-  linkVersion :: MonadThrow m => ByteString -> m Version
-  linkVersion bs = do
-    t <- throwEither $ E.decodeUtf8' bs
-    throwEither $ MP.parse parser "" t
+  linkVersion :: MonadThrow m => FilePath -> m Version
+  linkVersion = throwEither . MP.parse parser "" . T.pack
 
   parser
     =   MP.try (stripAbsolutePath *> cabalParse)
@@ -342,7 +326,7 @@ cabalSet = do
 -- | Get all installed hls, by matching on
 -- @~\/.ghcup\/bin/haskell-language-server-wrapper-<\hlsver\>@.
 getInstalledHLSs :: (MonadReader AppState m, MonadIO m, MonadCatch m)
-                 => m [Either (Path Rel) Version]
+                 => m [Either FilePath Version]
 getInstalledHLSs = do
   AppState { dirs = Dirs {..} } <- ask
   bins                          <- liftIO $ handleIO (\_ -> pure []) $ findFiles
@@ -353,7 +337,7 @@ getInstalledHLSs = do
     )
   forM bins $ \f ->
     case
-          fmap (version . decUTF8Safe) . B.stripPrefix "haskell-language-server-wrapper-" . toFilePath $ f
+          fmap (version . T.pack) . stripPrefix "haskell-language-server-wrapper-" $ f
       of
         Just (Right r) -> pure $ Right r
         Just (Left  _) -> pure $ Left f
@@ -362,7 +346,7 @@ getInstalledHLSs = do
 -- | Get all installed stacks, by matching on
 -- @~\/.ghcup\/bin/stack-<\stackver\>@.
 getInstalledStacks :: (MonadReader AppState m, MonadIO m, MonadCatch m)
-                   => m [Either (Path Rel) Version]
+                   => m [Either FilePath Version]
 getInstalledStacks = do
   AppState { dirs = Dirs {..} } <- ask
   bins                          <- liftIO $ handleIO (\_ -> pure []) $ findFiles
@@ -373,7 +357,7 @@ getInstalledStacks = do
     )
   forM bins $ \f ->
     case
-          fmap (version . decUTF8Safe) . B.stripPrefix "stack-" . toFilePath $ f
+          fmap (version . T.pack) . stripPrefix "stack-" $ f
       of
         Just (Right r) -> pure $ Right r
         Just (Left  _) -> pure $ Left f
@@ -384,20 +368,18 @@ getInstalledStacks = do
 stackSet :: (MonadReader AppState m, MonadIO m, MonadThrow m, MonadCatch m) => m (Maybe Version)
 stackSet = do
   AppState {dirs = Dirs {..}} <- ask
-  let stackBin = binDir </> [rel|stack|]
+  let stackBin = binDir </> "stack"
 
   liftIO $ handleIO' NoSuchThing (\_ -> pure Nothing) $ do
     broken <- isBrokenSymlink stackBin
     if broken
       then pure Nothing
       else do
-        link <- readSymbolicLink $ toFilePath stackBin
+        link <- liftIO $ getSymbolicLinkTarget stackBin
         Just <$> linkVersion link
  where
-  linkVersion :: MonadThrow m => ByteString -> m Version
-  linkVersion bs = do
-    t <- throwEither $ E.decodeUtf8' bs
-    throwEither $ MP.parse parser "" t
+  linkVersion :: MonadThrow m => FilePath -> m Version
+  linkVersion fp = throwEither $ MP.parse parser "" (T.pack fp)
    where
     parser =
       MP.chunk "stack-" *> version'
@@ -420,20 +402,18 @@ hlsInstalled ver = do
 hlsSet :: (MonadReader AppState m, MonadIO m, MonadThrow m, MonadCatch m) => m (Maybe Version)
 hlsSet = do
   AppState {dirs = Dirs {..}} <- ask
-  let hlsBin = binDir </> [rel|haskell-language-server-wrapper|]
+  let hlsBin = binDir </> "haskell-language-server-wrapper"
 
   liftIO $ handleIO' NoSuchThing (\_ -> pure Nothing) $ do
     broken <- isBrokenSymlink hlsBin
     if broken
       then pure Nothing
       else do
-        link <- readSymbolicLink $ toFilePath hlsBin
+        link <- liftIO $ getSymbolicLinkTarget hlsBin
         Just <$> linkVersion link
  where
-  linkVersion :: MonadThrow m => ByteString -> m Version
-  linkVersion bs = do
-    t <- throwEither $ E.decodeUtf8' bs
-    throwEither $ MP.parse parser "" t
+  linkVersion :: MonadThrow m => FilePath -> m Version
+  linkVersion fp = throwEither $ MP.parse parser "" (T.pack fp)
    where
     parser =
       MP.chunk "haskell-language-server-wrapper-" *> version'
@@ -452,13 +432,12 @@ hlsGHCVersions = do
     bins <- hlsServerBinaries h'
     pure $ fmap
       (version
-        . decUTF8Safe
+        . T.pack
         . fromJust
-        . B.stripPrefix "haskell-language-server-"
+        . stripPrefix "haskell-language-server-"
         . head
-        . B.split _tilde
-        . toFilePath
-      )
+        . splitOn "~"
+        )
       bins
   pure . rights . concat . maybeToList $ vers
 
@@ -466,7 +445,7 @@ hlsGHCVersions = do
 -- | Get all server binaries for an hls version, if any.
 hlsServerBinaries :: (MonadReader AppState m, MonadIO m)
                   => Version
-                  -> m [Path Rel]
+                  -> m [FilePath]
 hlsServerBinaries ver = do
   AppState { dirs = Dirs {..} } <- ask
   liftIO $ handleIO (\_ -> pure []) $ findFiles
@@ -482,7 +461,7 @@ hlsServerBinaries ver = do
 -- | Get the wrapper binary for an hls version, if any.
 hlsWrapperBinary :: (MonadReader AppState m, MonadThrow m, MonadIO m)
                  => Version
-                 -> m (Maybe (Path Rel))
+                 -> m (Maybe FilePath)
 hlsWrapperBinary ver = do
   AppState { dirs = Dirs {..} } <- ask
   wrapper                       <- liftIO $ handleIO (\_ -> pure []) $ findFiles
@@ -501,7 +480,7 @@ hlsWrapperBinary ver = do
 
 
 -- | Get all binaries for an hls version, if any.
-hlsAllBinaries :: (MonadReader AppState m, MonadIO m, MonadThrow m) => Version -> m [Path Rel]
+hlsAllBinaries :: (MonadReader AppState m, MonadIO m, MonadThrow m) => Version -> m [FilePath]
 hlsAllBinaries ver = do
   hls     <- hlsServerBinaries ver
   wrapper <- hlsWrapperBinary ver
@@ -509,7 +488,7 @@ hlsAllBinaries ver = do
 
 
 -- | Get the active symlinks for hls.
-hlsSymlinks :: (MonadReader AppState m, MonadIO m, MonadCatch m) => m [Path Rel]
+hlsSymlinks :: (MonadReader AppState m, MonadIO m, MonadCatch m) => m [FilePath]
 hlsSymlinks = do
   AppState { dirs = Dirs {..} } <- ask
   oldSyms                       <- liftIO $ handleIO (\_ -> pure []) $ findFiles
@@ -519,9 +498,8 @@ hlsSymlinks = do
                    ([s|^haskell-language-server-.*$|] :: ByteString)
     )
   filterM
-    ( fmap (== SymbolicLink)
-    . liftIO
-    . getFileType
+    ( liftIO
+    . pathIsSymbolicLink
     . (binDir </>)
     )
     oldSyms
@@ -585,61 +563,59 @@ getLatestGHCFor major' minor' dls =
 
 -- | Unpack an archive to a temporary directory and return that path.
 unpackToDir :: (MonadLogger m, MonadIO m, MonadThrow m)
-            => Path Abs       -- ^ destination dir
-            -> Path Abs       -- ^ archive path
+            => FilePath       -- ^ destination dir
+            -> FilePath       -- ^ archive path
             -> Excepts '[UnknownArchive
 #if !defined(TAR)
                         , ArchiveResult
 #endif
                         ] m ()
-unpackToDir dest av = do
-  fp <- decUTF8Safe . toFilePath <$> basename av
-  let dfp = decUTF8Safe . toFilePath $ dest
-  lift $ $(logInfo) [i|Unpacking: #{fp} to #{dfp}|]
-  fn <- toFilePath <$> basename av
+unpackToDir dfp av = do
+  let fn = takeFileName av
+  lift $ $(logInfo) [i|Unpacking: #{fn} to #{dfp}|]
 
 #if defined(TAR)
   let untar :: MonadIO m => BL.ByteString -> Excepts '[] m ()
-      untar = liftIO . Tar.unpack (toFilePath dest) . Tar.read
+      untar = liftIO . Tar.unpack dfp . Tar.read
 
-      rf :: MonadIO m => Path Abs -> Excepts '[] m BL.ByteString
-      rf = liftIO . readFile
+      rf :: MonadIO m => FilePath -> Excepts '[] m BL.ByteString
+      rf = liftIO . BL.readFile
 #else
   let untar :: MonadIO m => BL.ByteString -> Excepts '[ArchiveResult] m ()
-      untar = lEM . liftIO . runArchiveM . unpackToDirLazy (T.unpack . decUTF8Safe . toFilePath $ dest)
+      untar = lEM . liftIO . runArchiveM . unpackToDirLazy dfp
 
-      rf :: MonadIO m => Path Abs -> Excepts '[ArchiveResult] m BL.ByteString
-      rf = liftIO . readFile
+      rf :: MonadIO m => FilePath -> Excepts '[ArchiveResult] m BL.ByteString
+      rf = liftIO . BL.readFile
 #endif
 
   -- extract, depending on file extension
   if
-    | ".tar.gz" `B.isSuffixOf` fn -> liftE
+    | ".tar.gz" `isSuffixOf` fn -> liftE
       (untar . GZip.decompress =<< rf av)
-    | ".tar.xz" `B.isSuffixOf` fn -> do
+    | ".tar.xz" `isSuffixOf` fn -> do
       filecontents <- liftE $ rf av
       let decompressed = Lzma.decompress filecontents
       liftE $ untar decompressed
-    | ".tar.bz2" `B.isSuffixOf` fn ->
+    | ".tar.bz2" `isSuffixOf` fn ->
       liftE (untar . BZip.decompress =<< rf av)
-    | ".tar" `B.isSuffixOf` fn -> liftE (untar =<< rf av)
+    | ".tar" `isSuffixOf` fn -> liftE (untar =<< rf av)
     | otherwise -> throwE $ UnknownArchive fn
 
 
 getArchiveFiles :: (MonadLogger m, MonadIO m, MonadThrow m)
-                => Path Abs       -- ^ archive path
+                => FilePath       -- ^ archive path
                 -> Excepts '[UnknownArchive
 #if defined(TAR)
                             , Tar.FormatError
 #else
                             , ArchiveResult
 #endif
-                            ] m [ByteString]
+                            ] m [FilePath]
 getArchiveFiles av = do
-  fn <- toFilePath <$> basename av
+  let fn = takeFileName av
 
 #if defined(TAR)
-  let entries :: Monad m => BL.ByteString -> Excepts '[Tar.FormatError] m [ByteString]
+  let entries :: Monad m => BL.ByteString -> Excepts '[Tar.FormatError] m [FilePath]
       entries =
           lE @Tar.FormatError
           . Tar.foldEntries
@@ -648,34 +624,34 @@ getArchiveFiles av = do
             (\e -> Left e)
           . Tar.read
 
-      rf :: MonadIO m => Path Abs -> Excepts '[Tar.FormatError] m BL.ByteString
-      rf = liftIO . readFile
+      rf :: MonadIO m => FilePath -> Excepts '[Tar.FormatError] m BL.ByteString
+      rf = liftIO . BL.readFile
 #else
-  let entries :: Monad m => BL.ByteString -> Excepts '[ArchiveResult] m [ByteString]
-      entries = (fmap . fmap) (E.encodeUtf8 . T.pack . filepath) . lE . readArchiveBSL
+  let entries :: Monad m => BL.ByteString -> Excepts '[ArchiveResult] m [FilePath]
+      entries = (fmap . fmap) filepath . lE . readArchiveBSL
 
-      rf :: MonadIO m => Path Abs -> Excepts '[ArchiveResult] m BL.ByteString
-      rf = liftIO . readFile
+      rf :: MonadIO m => FilePath -> Excepts '[ArchiveResult] m BL.ByteString
+      rf = liftIO . BL.readFile
 #endif
 
   -- extract, depending on file extension
   if
-    | ".tar.gz" `B.isSuffixOf` fn -> liftE
+    | ".tar.gz" `isSuffixOf` fn -> liftE
       (entries . GZip.decompress =<< rf av)
-    | ".tar.xz" `B.isSuffixOf` fn -> do
+    | ".tar.xz" `isSuffixOf` fn -> do
       filecontents <- liftE $ rf av
       let decompressed = Lzma.decompress filecontents
       liftE $ entries decompressed
-    | ".tar.bz2" `B.isSuffixOf` fn ->
+    | ".tar.bz2" `isSuffixOf` fn ->
       liftE (entries . BZip.decompress =<< rf av)
-    | ".tar" `B.isSuffixOf` fn -> liftE (entries =<< rf av)
+    | ".tar" `isSuffixOf` fn -> liftE (entries =<< rf av)
     | otherwise -> throwE $ UnknownArchive fn
 
 
 intoSubdir :: (MonadLogger m, MonadIO m, MonadThrow m, MonadCatch m)
-           => Path Abs       -- ^ unpacked tar dir
+           => FilePath       -- ^ unpacked tar dir
            -> TarDir         -- ^ how to descend
-           -> Excepts '[TarDirDoesNotExist] m (Path Abs)
+           -> Excepts '[TarDirDoesNotExist] m (FilePath)
 intoSubdir bdir tardir = case tardir of
   RealDir pr -> do
     whenM (fmap not . liftIO . doesDirectoryExist $ (bdir </> pr))
@@ -743,10 +719,9 @@ getDownloader = ask <&> downloader . settings
     -------------
 
 
-urlBaseName :: MonadThrow m
-            => ByteString  -- ^ the url path (without scheme and host)
-            -> m (Path Rel)
-urlBaseName = parseRel . snd . B.breakEnd (== _slash) . urlDecode False
+urlBaseName :: ByteString  -- ^ the url path (without scheme and host)
+            -> ByteString
+urlBaseName = snd . B.breakEnd (== _slash) . urlDecode False
 
 
 -- | Get tool files from @~\/.ghcup\/bin\/ghc\/\<ver\>\/bin\/\*@
@@ -757,16 +732,16 @@ urlBaseName = parseRel . snd . B.breakEnd (== _slash) . urlDecode False
 --   - @["hsc2hs","haddock","hpc","runhaskell","ghc","ghc-pkg","ghci","runghc","hp2ps"]@
 ghcToolFiles :: (MonadReader AppState m, MonadThrow m, MonadFail m, MonadIO m)
              => GHCTargetVersion
-             -> Excepts '[NotInstalled] m [Path Rel]
+             -> Excepts '[NotInstalled] m [FilePath]
 ghcToolFiles ver = do
   ghcdir <- lift $ ghcupGHCDir ver
-  let bindir = ghcdir </> [rel|bin|]
+  let bindir = ghcdir </> "bin"
 
   -- fail if ghc is not installed
   whenM (fmap not $ liftIO $ doesDirectoryExist ghcdir)
         (throwE (NotInstalled GHC ver))
 
-  files    <- liftIO $ getDirsFiles' bindir
+  files    <- liftIO $ listDirectory bindir
   -- figure out the <ver> suffix, because this might not be `Version` for
   -- alpha/rc releases, but x.y.a.somedate.
 
@@ -785,75 +760,73 @@ ghcToolFiles ver = do
     then pure id
     else do
       (Just symver) <-
-        B.stripPrefix (toFilePath ghcbin <> "-") . takeFileName
-          <$> liftIO (readSymbolicLink $ toFilePath ghcbinPath)
-      when (B.null symver)
+        stripPrefix (ghcbin <> "-") . takeFileName
+          <$> liftIO (getSymbolicLinkTarget ghcbinPath)
+      when (null symver)
            (throwIO $ userError "Fatal: ghc symlink target is broken")
-      pure $ filter (\x -> not $ symver `B.isSuffixOf` toFilePath x)
+      pure $ filter (\x -> not $ symver `isSuffixOf` x)
 
   pure $ onlyUnversioned files
  where
     -- GHC is moving some builds to Hadrian for bindists,
     -- which doesn't create versioned binaries.
     -- https://gitlab.haskell.org/haskell/ghcup-hs/issues/31
-  isHadrian :: Path Abs -- ^ ghcbin path
+  isHadrian :: FilePath -- ^ ghcbin path
             -> IO Bool
-  isHadrian = fmap (/= SymbolicLink) . getFileType
+  isHadrian = pathIsSymbolicLink
 
 
 -- | This file, when residing in @~\/.ghcup\/ghc\/\<ver\>\/@ signals that
 -- this GHC was built from source. It contains the build config.
-ghcUpSrcBuiltFile :: Path Rel
-ghcUpSrcBuiltFile = [rel|.ghcup_src_built|]
+ghcUpSrcBuiltFile :: FilePath
+ghcUpSrcBuiltFile = ".ghcup_src_built"
 
 
 -- | Calls gmake if it exists in PATH, otherwise make.
 make :: (MonadThrow m, MonadIO m, MonadReader AppState m)
-     => [ByteString]
-     -> Maybe (Path Abs)
+     => [String]
+     -> Maybe (FilePath)
      -> m (Either ProcessError ())
 make args workdir = do
-  spaths    <- catMaybes . fmap parseAbs <$> liftIO getSearchPath
-  has_gmake <- isJust <$> liftIO (searchPath spaths [rel|gmake|])
+  spaths    <- liftIO getSearchPath
+  has_gmake <- isJust <$> liftIO (searchPath spaths "gmake")
   let mymake = if has_gmake then "gmake" else "make"
-  execLogged mymake True args [rel|ghc-make|] workdir Nothing
+  execLogged mymake args workdir "ghc-make" Nothing
 
-makeOut :: [ByteString]
-        -> Maybe (Path Abs)
+makeOut :: [String]
+        -> Maybe (FilePath)
         -> IO CapturedProcess
 makeOut args workdir = do
-  spaths    <- catMaybes . fmap parseAbs <$> liftIO getSearchPath
-  has_gmake <- isJust <$> liftIO (searchPath spaths [rel|gmake|])
-  let mymake = if has_gmake then [rel|gmake|] else [rel|make|]
+  spaths    <- liftIO getSearchPath
+  has_gmake <- isJust <$> liftIO (searchPath spaths "gmake")
+  let mymake = if has_gmake then "gmake" else "make"
   liftIO $ executeOut mymake args workdir
 
 
 -- | Try to apply patches in order. Fails with 'PatchFailed'
 -- on first failure.
 applyPatches :: (MonadLogger m, MonadIO m)
-             => Path Abs   -- ^ dir containing patches
-             -> Path Abs   -- ^ dir to apply patches in
+             => FilePath   -- ^ dir containing patches
+             -> FilePath   -- ^ dir to apply patches in
              -> Excepts '[PatchFailed] m ()
 applyPatches pdir ddir = do
-  patches <- liftIO $ getDirsFiles pdir
+  patches <- (fmap . fmap) (pdir </>) $ liftIO $ listDirectory pdir
   forM_ (sort patches) $ \patch' -> do
     lift $ $(logInfo) [i|Applying patch #{patch'}|]
     fmap (either (const Nothing) Just)
          (liftIO $ exec
            "patch"
-           True
-           ["-p1", "-i", toFilePath patch']
+           ["-p1", "-i", patch']
            (Just ddir)
            Nothing)
       !? PatchFailed
 
 
 -- | https://gitlab.haskell.org/ghc/ghc/-/issues/17353
-darwinNotarization :: Platform -> Path Abs -> IO (Either ProcessError ())
+darwinNotarization :: Platform -> FilePath -> IO (Either ProcessError ())
 darwinNotarization Darwin path = exec
   "xattr"
-  True
-  ["-r", "-d", "com.apple.quarantine", toFilePath path]
+  ["-r", "-d", "com.apple.quarantine", path]
   Nothing
   Nothing
 darwinNotarization _ _ = pure $ Right ()
@@ -871,19 +844,19 @@ getChangeLog dls tool (Right tag) =
 --   1. the build directory, depending on the KeepDirs setting
 --   2. the install destination, depending on whether the build failed
 runBuildAction :: (Show (V e), MonadReader AppState m, MonadIO m, MonadMask m)
-               => Path Abs          -- ^ build directory (cleaned up depending on Settings)
-               -> Maybe (Path Abs)  -- ^ dir to *always* clean up on exception
+               => FilePath          -- ^ build directory (cleaned up depending on Settings)
+               -> Maybe FilePath  -- ^ dir to *always* clean up on exception
                -> Excepts e m a
                -> Excepts '[BuildFailed] m a
 runBuildAction bdir instdir action = do
   AppState { settings = Settings {..} } <- lift ask
   let exAction = do
         forM_ instdir $ \dir ->
-          liftIO $ hideError doesNotExistErrorType $ deleteDirRecursive dir
+          liftIO $ hideError doesNotExistErrorType $ removeDirectoryRecursive dir
         when (keepDirs == Never)
           $ liftIO
           $ hideError doesNotExistErrorType
-          $ deleteDirRecursive bdir
+          $ removeDirectoryRecursive bdir
   v <-
     flip onException exAction
     $ catchAllE
@@ -892,28 +865,28 @@ runBuildAction bdir instdir action = do
           throwE (BuildFailed bdir es)
         ) action
 
-  when (keepDirs == Never || keepDirs == Errors) $ liftIO $ deleteDirRecursive
+  when (keepDirs == Never || keepDirs == Errors) $ liftIO $ removeDirectoryRecursive
     bdir
   pure v
 
 
 -- | More permissive version of 'createDirRecursive'. This doesn't
 -- error when the destination is a symlink to a directory.
-createDirRecursive' :: Path b -> IO ()
+createDirRecursive' :: FilePath -> IO ()
 createDirRecursive' p =
   handleIO (\e -> if isAlreadyExistsError e then isSymlinkDir e else throwIO e)
-    . createDirRecursive newDirPerms
+    . createDirectoryIfMissing True
     $ p
 
  where
   isSymlinkDir e = do
-    ft <- getFileType p
+    ft <- pathIsSymbolicLink p
     case ft of
-      SymbolicLink -> do
+      True -> do
         rp <- canonicalizePath p
-        rft <- getFileType rp
+        rft <- doesDirectoryExist rp
         case rft of
-          Directory -> pure ()
+          True -> pure ()
           _ -> throwIO e
       _ -> throwIO e
 
diff --git a/lib/GHCup/Utils/Dirs.hs b/lib/GHCup/Utils/Dirs.hs
index 2838c52f7e745c189889d0167807c76e502d9175..61266e6216430830c377277a43cd3fca741013b4 100644
--- a/lib/GHCup/Utils/Dirs.hs
+++ b/lib/GHCup/Utils/Dirs.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP                   #-}
 {-# LANGUAGE DataKinds             #-}
 {-# LANGUAGE OverloadedStrings     #-}
 {-# LANGUAGE FlexibleContexts      #-}
@@ -12,7 +13,7 @@ Copyright   : (c) Julian Ospald, 2020
 License     : LGPL-3.0
 Maintainer  : hasufell@hasufell.de
 Stability   : experimental
-Portability : POSIX
+Portability : portable
 -}
 module GHCup.Utils.Dirs
   ( getDirs
@@ -34,7 +35,6 @@ import           GHCup.Types.JSON               ( )
 import           GHCup.Utils.MegaParsec
 import           GHCup.Utils.Prelude
 
-import           Control.Applicative
 import           Control.Exception.Safe
 import           Control.Monad
 import           Control.Monad.IO.Unlift
@@ -42,32 +42,20 @@ import           Control.Monad.Logger
 import           Control.Monad.Reader
 import           Control.Monad.Trans.Resource hiding (throwM)
 import           Data.Bifunctor
-import           Data.ByteString                ( ByteString )
 import           Data.Maybe
 import           Data.String.Interpolate
 import           GHC.IO.Exception               ( IOErrorType(NoSuchThing) )
 import           Haskus.Utils.Variant.Excepts
-import           HPath
-import           HPath.IO
 import           Optics
-import           Prelude                 hiding ( abs
-                                                , readFile
-                                                , writeFile
-                                                )
+import           System.Directory
 import           System.DiskSpace                                                
-import           System.Posix.Env.ByteString    ( getEnv
-                                                , getEnvDefault
-                                                )
-import           System.Posix.FilePath   hiding ( (</>) )
-import           System.Posix.Temp.ByteString   ( mkdtemp )
-
-import qualified Data.ByteString.Lazy          as L
-import qualified Data.ByteString.UTF8          as UTF8
+import           System.Environment
+import           System.FilePath
+import           System.IO.Temp
+
+import qualified Data.ByteString               as BS
 import qualified Data.Text                     as T
-import qualified Data.Text.Encoding            as E
 import qualified Data.Yaml                     as Y
-import qualified System.Posix.FilePath         as FP
-import qualified System.Posix.User             as PU
 import qualified Text.Megaparsec               as MP
 import Control.Concurrent (threadDelay)
 
@@ -82,96 +70,96 @@ import Control.Concurrent (threadDelay)
 --
 -- If 'GHCUP_USE_XDG_DIRS' is set (to anything),
 -- then uses 'XDG_DATA_HOME/ghcup' as per xdg spec.
-ghcupBaseDir :: IO (Path Abs)
+ghcupBaseDir :: IO FilePath
 ghcupBaseDir = do
   xdg <- useXDG
   if xdg
     then do
-      bdir <- getEnv "XDG_DATA_HOME" >>= \case
-        Just r  -> parseAbs r
+      bdir <- lookupEnv "XDG_DATA_HOME" >>= \case
+        Just r  -> pure r
         Nothing -> do
           home <- liftIO getHomeDirectory
-          pure (home </> [rel|.local/share|])
-      pure (bdir </> [rel|ghcup|])
+          pure (home </> ".local/share")
+      pure (bdir </> "ghcup")
     else do
-      bdir <- getEnv "GHCUP_INSTALL_BASE_PREFIX" >>= \case
-        Just r  -> parseAbs r
+      bdir <- lookupEnv "GHCUP_INSTALL_BASE_PREFIX" >>= \case
+        Just r  -> pure r
         Nothing -> liftIO getHomeDirectory
-      pure (bdir </> [rel|.ghcup|])
+      pure (bdir </> ".ghcup")
 
 
 -- | ~/.ghcup by default
 --
 -- If 'GHCUP_USE_XDG_DIRS' is set (to anything),
 -- then uses 'XDG_CONFIG_HOME/ghcup' as per xdg spec.
-ghcupConfigDir :: IO (Path Abs)
+ghcupConfigDir :: IO FilePath
 ghcupConfigDir = do
   xdg <- useXDG
   if xdg
     then do
-      bdir <- getEnv "XDG_CONFIG_HOME" >>= \case
-        Just r  -> parseAbs r
+      bdir <- lookupEnv "XDG_CONFIG_HOME" >>= \case
+        Just r  -> pure r
         Nothing -> do
           home <- liftIO getHomeDirectory
-          pure (home </> [rel|.config|])
-      pure (bdir </> [rel|ghcup|])
+          pure (home </> ".config")
+      pure (bdir </> "ghcup")
     else do
-      bdir <- getEnv "GHCUP_INSTALL_BASE_PREFIX" >>= \case
-        Just r  -> parseAbs r
+      bdir <- lookupEnv "GHCUP_INSTALL_BASE_PREFIX" >>= \case
+        Just r  -> pure r
         Nothing -> liftIO getHomeDirectory
-      pure (bdir </> [rel|.ghcup|])
+      pure (bdir </> ".ghcup")
 
 
 -- | If 'GHCUP_USE_XDG_DIRS' is set (to anything),
 -- then uses 'XDG_BIN_HOME' env var or defaults to '~/.local/bin'
 -- (which, sadly is not strictly xdg spec).
-ghcupBinDir :: IO (Path Abs)
+ghcupBinDir :: IO FilePath
 ghcupBinDir = do
   xdg <- useXDG
   if xdg
     then do
-      getEnv "XDG_BIN_HOME" >>= \case
-        Just r  -> parseAbs r
+      lookupEnv "XDG_BIN_HOME" >>= \case
+        Just r  -> pure r
         Nothing -> do
           home <- liftIO getHomeDirectory
-          pure (home </> [rel|.local/bin|])
-    else ghcupBaseDir <&> (</> [rel|bin|])
+          pure (home </> ".local/bin")
+    else ghcupBaseDir <&> (</> "bin")
 
 
 -- | Defaults to '~/.ghcup/cache'.
 --
 -- If 'GHCUP_USE_XDG_DIRS' is set (to anything),
 -- then uses 'XDG_CACHE_HOME/ghcup' as per xdg spec.
-ghcupCacheDir :: IO (Path Abs)
+ghcupCacheDir :: IO FilePath
 ghcupCacheDir = do
   xdg <- useXDG
   if xdg
     then do
-      bdir <- getEnv "XDG_CACHE_HOME" >>= \case
-        Just r  -> parseAbs r
+      bdir <- lookupEnv "XDG_CACHE_HOME" >>= \case
+        Just r  -> pure r
         Nothing -> do
           home <- liftIO getHomeDirectory
-          pure (home </> [rel|.cache|])
-      pure (bdir </> [rel|ghcup|])
-    else ghcupBaseDir <&> (</> [rel|cache|])
+          pure (home </> ".cache")
+      pure (bdir </> "ghcup")
+    else ghcupBaseDir <&> (</> "cache")
 
 
 -- | Defaults to '~/.ghcup/logs'.
 --
 -- If 'GHCUP_USE_XDG_DIRS' is set (to anything),
 -- then uses 'XDG_CACHE_HOME/ghcup/logs' as per xdg spec.
-ghcupLogsDir :: IO (Path Abs)
+ghcupLogsDir :: IO FilePath
 ghcupLogsDir = do
   xdg <- useXDG
   if xdg
     then do
-      bdir <- getEnv "XDG_CACHE_HOME" >>= \case
-        Just r  -> parseAbs r
+      bdir <- lookupEnv "XDG_CACHE_HOME" >>= \case
+        Just r  -> pure r
         Nothing -> do
           home <- liftIO getHomeDirectory
-          pure (home </> [rel|.cache|])
-      pure (bdir </> [rel|ghcup/logs|])
-    else ghcupBaseDir <&> (</> [rel|logs|])
+          pure (home </> ".cache")
+      pure (bdir </> "ghcup/logs")
+    else ghcupBaseDir <&> (</> "logs")
 
 
 getDirs :: IO Dirs
@@ -194,11 +182,11 @@ ghcupConfigFile :: (MonadIO m)
                 => Excepts '[JSONError] m UserSettings
 ghcupConfigFile = do
   confDir <- liftIO ghcupConfigDir
-  let file = confDir </> [rel|config.yaml|]
-  bs <- liftIO $ handleIO' NoSuchThing (\_ -> pure Nothing) $ Just <$> readFile file
-  case bs of
+  let file = confDir </> "config.yaml"
+  contents <- liftIO $ handleIO' NoSuchThing (\_ -> pure Nothing) $ Just <$> BS.readFile file
+  case contents of
       Nothing -> pure defaultUserSettings
-      Just bs' -> lE' JSONDecodeError . first show . Y.decodeEither' . L.toStrict $ bs'
+      Just contents' -> lE' JSONDecodeError . first show . Y.decodeEither' $ contents'
 
 
     -------------------------
@@ -207,10 +195,10 @@ ghcupConfigFile = do
 
 
 -- | ~/.ghcup/ghc by default.
-ghcupGHCBaseDir :: (MonadReader AppState m) => m (Path Abs)
+ghcupGHCBaseDir :: (MonadReader AppState m) => m FilePath
 ghcupGHCBaseDir = do
   AppState { dirs = Dirs {..} } <- ask
-  pure (baseDir </> [rel|ghc|])
+  pure (baseDir </> "ghc")
 
 
 -- | Gets '~/.ghcup/ghc/<ghcupGHCDir>'.
@@ -219,35 +207,33 @@ ghcupGHCBaseDir = do
 --   * 8.8.4
 ghcupGHCDir :: (MonadReader AppState m, MonadThrow m)
             => GHCTargetVersion
-            -> m (Path Abs)
+            -> m FilePath
 ghcupGHCDir ver = do
-  ghcbasedir    <- ghcupGHCBaseDir
-  verdir        <- parseRel $ E.encodeUtf8 (tVerToText ver)
+  ghcbasedir <- ghcupGHCBaseDir
+  let verdir = T.unpack $ tVerToText ver
   pure (ghcbasedir </> verdir)
 
 
 -- | See 'ghcupToolParser'.
-parseGHCupGHCDir :: MonadThrow m => Path Rel -> m GHCTargetVersion
-parseGHCupGHCDir (toFilePath -> f) = do
-  fp <- throwEither $ E.decodeUtf8' f
+parseGHCupGHCDir :: MonadThrow m => FilePath -> m GHCTargetVersion
+parseGHCupGHCDir (T.pack -> fp) =
   throwEither $ MP.parse ghcTargetVerP "" fp
 
 
-mkGhcupTmpDir :: (MonadUnliftIO m, MonadLogger m, MonadCatch m, MonadThrow m, MonadIO m) => m (Path Abs)
+mkGhcupTmpDir :: (MonadUnliftIO m, MonadLogger m, MonadCatch m, MonadThrow m, MonadIO m) => m FilePath
 mkGhcupTmpDir = do
-  tmpdir <- liftIO $ getEnvDefault "TMPDIR" "/tmp"
-  let fp = T.unpack $ decUTF8Safe tmpdir
+  tmpdir <- liftIO $ fromMaybe "/tmp" <$> lookupEnv "TMPDIR"
 
   let minSpace = 5000 -- a rough guess, aight?
-  space <- handleIO (\_ -> pure Nothing) $ fmap Just $ liftIO $ getAvailSpace fp
+  space <- handleIO (\_ -> pure Nothing) $ fmap Just $ liftIO $ getAvailSpace tmpdir
   when (maybe False (toBytes minSpace >) space) $ do
-    $(logWarn) [i|Possibly insufficient disk space on #{fp}. At least #{minSpace} MB are recommended, but only #{toMB (fromJust space)} are free. Consider freeing up disk space or setting TMPDIR env variable.|]
+    $(logWarn) [i|Possibly insufficient disk space on #{tmpdir}. At least #{minSpace} MB are recommended, but only #{toMB (fromJust space)} are free. Consider freeing up disk space or setting TMPDIR env variable.|]
     $(logWarn)
       "...waiting for 10 seconds before continuing anyway, you can still abort..."
     liftIO $ threadDelay 10000000 -- give the user a sec to intervene
 
-  tmp <- liftIO $ mkdtemp (tmpdir FP.</> "ghcup-")
-  parseAbs tmp
+  tmp <- liftIO $ createTempDirectory tmpdir "ghcup-"
+  pure tmp
  where
   toBytes mb = mb * 1024 * 1024
   toMB b = show (truncate' (fromIntegral b / (1024 * 1024) :: Double) 2)
@@ -256,8 +242,8 @@ mkGhcupTmpDir = do
       where t = 10^n
 
 
-withGHCupTmpDir :: (MonadUnliftIO m, MonadLogger m, MonadCatch m, MonadResource m, MonadThrow m, MonadIO m) => m (Path Abs)
-withGHCupTmpDir = snd <$> withRunInIO (\run -> run $ allocate (run mkGhcupTmpDir) deleteDirRecursive)
+withGHCupTmpDir :: (MonadUnliftIO m, MonadLogger m, MonadCatch m, MonadResource m, MonadThrow m, MonadIO m) => m FilePath
+withGHCupTmpDir = snd <$> withRunInIO (\run -> run $ allocate (run mkGhcupTmpDir) removeDirectoryRecursive)
 
 
 
@@ -267,24 +253,14 @@ withGHCupTmpDir = snd <$> withRunInIO (\run -> run $ allocate (run mkGhcupTmpDir
     --------------
 
 
-getHomeDirectory :: IO (Path Abs)
-getHomeDirectory = do
-  e <- getEnv "HOME"
-  case e of
-    Just fp -> parseAbs fp
-    Nothing -> do
-      h <- PU.homeDirectory <$> (PU.getEffectiveUserID >>= PU.getUserEntryForID)
-      parseAbs $ UTF8.fromString h -- this is a guess
-
-
 useXDG :: IO Bool
-useXDG = isJust <$> getEnv "GHCUP_USE_XDG_DIRS"
+useXDG = isJust <$> lookupEnv "GHCUP_USE_XDG_DIRS"
 
 
-relativeSymlink :: Path Abs  -- ^ the path in which to create the symlink
-                -> Path Abs  -- ^ the symlink destination
-                -> ByteString
-relativeSymlink (toFilePath -> p1) (toFilePath -> p2) =
+relativeSymlink :: FilePath  -- ^ the path in which to create the symlink
+                -> FilePath  -- ^ the symlink destination
+                -> FilePath
+relativeSymlink p1 p2 =
   let d1      = splitDirectories p1
       d2      = splitDirectories p2
       common  = takeWhile (\(x, y) -> x == y) $ zip d1 d2
diff --git a/lib/GHCup/Utils/File.hs b/lib/GHCup/Utils/File.hs
index e782839d70895b523bcbe184b6febcda8e2ab17d..9f74867fa913281514596f259b9e3d0ff272d13d 100644
--- a/lib/GHCup/Utils/File.hs
+++ b/lib/GHCup/Utils/File.hs
@@ -1,494 +1,17 @@
-{-# LANGUAGE QuasiQuotes       #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE FlexibleContexts  #-}
-{-# LANGUAGE TemplateHaskell   #-}
-{-# LANGUAGE ViewPatterns      #-}
-
-{-|
-Module      : GHCup.Utils.File
-Description : File and unix APIs
-Copyright   : (c) Julian Ospald, 2020
-License     : LGPL-3.0
-Maintainer  : hasufell@hasufell.de
-Stability   : experimental
-Portability : POSIX
-
-This module handles file and executable handling.
-Some of these functions use sophisticated logging.
--}
-module GHCup.Utils.File where
-
-import           GHCup.Utils.Prelude
-import           GHCup.Types
-
-import           Control.Concurrent
-import           Control.Concurrent.Async
-import           Control.Exception              ( evaluate )
-import           Control.Exception.Safe
-import           Control.Monad
-import           Control.Monad.Logger
-import           Control.Monad.Reader
-import           Control.Monad.Trans.State.Strict
-import           Data.ByteString                ( ByteString )
-import           Data.Foldable
-import           Data.Functor
-import           Data.IORef
-import           Data.Maybe
-import           Data.Sequence                  ( Seq, (|>) )
-import           Data.String.Interpolate
-import           Data.Text                      ( Text )
-import           Data.Void
-import           Data.Word8
-import           GHC.IO.Exception
-import           HPath
-import           HPath.IO                hiding ( hideError )
-import           Optics                  hiding ((<|), (|>))
-import           System.Console.Pretty   hiding ( Pretty )
-import           System.Console.Regions
-import           System.IO.Error
-import           System.Posix.Directory.ByteString
-import           System.Posix.FD               as FD
-import           System.Posix.FilePath   hiding ( (</>) )
-import           System.Posix.Files.ByteString
-import           System.Posix.Foreign           ( oExcl, oAppend )
-import "unix"    System.Posix.IO.ByteString
-                                         hiding ( openFd )
-import           System.Posix.Process           ( ProcessStatus(..) )
-import           System.Posix.Types
-import           Text.PrettyPrint.HughesPJClass hiding ( (<>) )
-import           Text.Regex.Posix
-
-
-import qualified Control.Exception             as EX
-import qualified Data.Sequence                 as Sq
-import qualified Data.Text                     as T
-import qualified Data.Text.Encoding            as E
-import qualified System.Posix.Process.ByteString
-                                               as SPPB
-import           Streamly.External.Posix.DirStream
-import qualified Streamly.Prelude              as S
-import qualified Text.Megaparsec               as MP
-import qualified Data.ByteString               as BS
-import qualified "unix-bytestring" System.Posix.IO.ByteString
-                                               as SPIB
-
-
-
-data ProcessError = NonZeroExit Int ByteString [ByteString]
-                  | PTerminated ByteString [ByteString]
-                  | PStopped ByteString [ByteString]
-                  | NoSuchPid ByteString [ByteString]
-                  deriving Show
-
-instance Pretty ProcessError where
-  pPrint (NonZeroExit e exe args) =
-    text [i|Process "#{decUTF8Safe exe}" with arguments #{fmap decUTF8Safe args} failed with exit code #{e}.|]
-  pPrint (PTerminated exe args) =
-    text [i|Process "#{decUTF8Safe exe}" with arguments #{fmap decUTF8Safe args} terminated.|]
-  pPrint (PStopped exe args) =
-    text [i|Process "#{decUTF8Safe exe}" with arguments #{fmap decUTF8Safe args} stopped.|]
-  pPrint (NoSuchPid exe args) =
-    text [i|Could not find PID for process running "#{decUTF8Safe exe}" with arguments #{fmap decUTF8Safe args}.|]
-
-data CapturedProcess = CapturedProcess
-  { _exitCode :: ExitCode
-  , _stdOut   :: ByteString
-  , _stdErr   :: ByteString
-  }
-  deriving (Eq, Show)
-
-makeLenses ''CapturedProcess
-
-
--- | Find the given executable by searching all *absolute* PATH components.
--- Relative paths in PATH are ignored.
---
--- This shouldn't throw IO exceptions, unless getting the environment variable
--- PATH does.
-findExecutable :: Path Rel -> IO (Maybe (Path Abs))
-findExecutable ex = do
-  sPaths <- fmap (catMaybes . fmap parseAbs) getSearchPath
-  -- We don't want exceptions to mess up our result. If we can't
-  -- figure out if a file exists, then treat it as a negative result.
-  asum $ fmap
-    (handleIO (\_ -> pure Nothing)
-      -- asum for short-circuiting behavior
-      . (\s' -> (isExecutable (s' </> ex) >>= guard) $> Just (s' </> ex))
-    )
-    sPaths
-
-
--- | Execute the given command and collect the stdout, stderr and the exit code.
--- The command is run in a subprocess.
-executeOut :: Path b            -- ^ command as filename, e.g. 'ls'
-           -> [ByteString]      -- ^ arguments to the command
-           -> Maybe (Path Abs)  -- ^ chdir to this path
-           -> IO CapturedProcess
-executeOut path args chdir = captureOutStreams $ do
-  maybe (pure ()) (changeWorkingDirectory . toFilePath) chdir
-  SPPB.executeFile (toFilePath path) True args Nothing
-
-
-execLogged :: (MonadReader AppState m, MonadIO m, MonadThrow m)
-           => ByteString       -- ^ thing to execute
-           -> Bool             -- ^ whether to search PATH for the thing
-           -> [ByteString]     -- ^ args for the thing
-           -> Path Rel         -- ^ log filename (opened in append mode)
-           -> Maybe (Path Abs) -- ^ optionally chdir into this
-           -> Maybe [(ByteString, ByteString)] -- ^ optional environment
-           -> m (Either ProcessError ())
-execLogged exe spath args lfile chdir env = do
-  AppState { settings = Settings {..}, dirs = Dirs {..} } <- ask
-  logfile       <- (logsDir </>) <$> parseRel (toFilePath lfile <> ".log")
-  liftIO $ bracket (openFd (toFilePath logfile) WriteOnly [oAppend] (Just newFilePerms))
-                   closeFd
-                   (action verbose)
- where
-  action verbose fd = do
-    actionWithPipes $ \(stdoutRead, stdoutWrite) -> do
-      -- start the thread that logs to stdout
-      pState <- newEmptyMVar
-      done   <- newEmptyMVar
-      void
-        $ forkIO
-        $ EX.handle (\(_ :: IOException) -> pure ())
-        $ EX.finally
-            (if verbose
-              then tee fd stdoutRead
-              else printToRegion fd stdoutRead 6 pState
-            )
-            (putMVar done ())
-
-      -- fork the subprocess
-      pid <- SPPB.forkProcess $ do
-        void $ dupTo stdoutWrite stdOutput
-        void $ dupTo stdoutWrite stdError
-        closeFd stdoutRead
-        closeFd stdoutWrite
-
-        -- execute the action
-        maybe (pure ()) (changeWorkingDirectory . toFilePath) chdir
-        void $ SPPB.executeFile exe spath args env
-
-      closeFd stdoutWrite
-
-      -- wait for the subprocess to finish
-      e <- toProcessError exe args <$!> SPPB.getProcessStatus True True pid
-      putMVar pState (either (const False) (const True) e)
-
-      void $ race (takeMVar done) (threadDelay (1000000 * 3))
-      closeFd stdoutRead
-
-      pure e
-
-  tee :: Fd -> Fd -> IO ()
-  tee fileFd fdIn = readTilEOF lineAction fdIn
-
-   where
-    lineAction :: ByteString -> IO ()
-    lineAction bs' = do
-      void $ SPIB.fdWrite fileFd (bs' <> "\n")
-      void $ SPIB.fdWrite stdOutput (bs' <> "\n")
-
-  -- Reads fdIn and logs the output in a continous scrolling area
-  -- of 'size' terminal lines. Also writes to a log file.
-  printToRegion :: Fd -> Fd -> Int -> MVar Bool -> IO ()
-  printToRegion fileFd fdIn size pState = do
-    void $ displayConsoleRegions $ do
-      rs <-
-        liftIO
-        . fmap Sq.fromList
-        . sequence
-        . replicate size
-        . openConsoleRegion
-        $ Linear
-      flip runStateT mempty
-        $ handle
-            (\(ex :: SomeException) -> do
-              ps <- liftIO $ takeMVar pState
-              when ps (forM_ rs (liftIO . closeConsoleRegion))
-              throw ex
-            )
-        $ readTilEOF (lineAction rs) fdIn
-
-   where
-    -- action to perform line by line
-    -- TODO: do this with vty for efficiency
-    lineAction :: (MonadMask m, MonadIO m)
-               => Seq ConsoleRegion
-               -> ByteString
-               -> StateT (Seq ByteString) m ()
-    lineAction rs = \bs' -> do
-      void $ liftIO $ SPIB.fdWrite fileFd (bs' <> "\n")
-      modify (swapRegs bs')
-      regs <- get
-      liftIO $ forM_ (Sq.zip regs rs) $ \(bs, r) -> setConsoleRegion r $ do
-        w <- consoleWidth
-        return
-          . T.pack
-          . color Blue
-          . T.unpack
-          . decUTF8Safe
-          . trim w
-          . (\b -> "[ " <> toFilePath lfile <> " ] " <> b)
-          $ bs
-
-    swapRegs :: a -> Seq a -> Seq a
-    swapRegs bs = \regs -> if
-      | Sq.length regs < size -> regs |> bs
-      | otherwise             -> Sq.drop 1 regs |> bs
-
-    -- trim output line to terminal width
-    trim :: Int -> ByteString -> ByteString
-    trim w = \bs -> if
-      | BS.length bs > w && w > 5 -> BS.take (w - 4) bs <> "..."
-      | otherwise                 -> bs
-
-  -- Consecutively read from Fd in 512 chunks until we hit
-  -- newline or EOF.
-  readLine :: MonadIO m
-           => Fd          -- ^ input file descriptor
-           -> ByteString  -- ^ rest buffer (read across newline)
-           -> m (ByteString, ByteString, Bool) -- ^ (full line, rest, eof)
-  readLine fd = go
-   where
-    go inBs = do
-      -- if buffer is not empty, process it first
-      mbs <- if BS.length inBs == 0
-               -- otherwise attempt read
-               then liftIO
-                    $ handleIO (\e -> if isEOFError e then pure Nothing else ioError e)
-                    $ fmap Just
-                    $ SPIB.fdRead fd 512
-               else pure $ Just inBs
-      case mbs of
-        Nothing -> pure ("", "", True)
-        Just bs -> do
-          -- split on newline
-          let (line, rest) = BS.span (/= _lf) bs
-          if
-            | BS.length rest /= 0 -> pure (line, BS.tail rest, False)
-            -- if rest is empty, then there was no newline, process further
-            | otherwise           -> (\(l, r, b) -> (line <> l, r, b)) <$!> go mempty
-
-  readTilEOF :: MonadIO m => (ByteString -> m a) -> Fd -> m ()
-  readTilEOF ~action' fd' = go mempty
-   where
-    go bs' = do
-      (bs, rest, eof) <- readLine fd' bs'
-      if eof
-         then liftIO $ ioError (mkIOError eofErrorType "" Nothing Nothing)
-         else void (action' bs) >> go rest
-
-
--- | Capture the stdout and stderr of the given action, which
--- is run in a subprocess. Stdin is closed. You might want to
--- 'race' this to make sure it terminates.
-captureOutStreams :: IO a
-                     -- ^ the action to execute in a subprocess
-                  -> IO CapturedProcess
-captureOutStreams action = do
-  actionWithPipes $ \(parentStdoutRead, childStdoutWrite) ->
-    actionWithPipes $ \(parentStderrRead, childStderrWrite) -> do
-      pid <- SPPB.forkProcess $ do
-        -- dup stdout
-        void $ dupTo childStdoutWrite stdOutput
-        closeFd childStdoutWrite
-        closeFd parentStdoutRead
-
-        -- dup stderr
-        void $ dupTo childStderrWrite stdError
-        closeFd childStderrWrite
-        closeFd parentStderrRead
-
-        -- execute the action
-        a <- action
-        void $ evaluate a
-
-      -- close everything we don't need
-      closeFd childStdoutWrite
-      closeFd childStderrWrite
-
-      -- start thread that writes the output
-      refOut <- newIORef BS.empty
-      refErr <- newIORef BS.empty
-      done   <- newEmptyMVar
-      _      <-
-        forkIO
-        $ EX.handle (\(_ :: IOException) -> pure ())
-        $ flip EX.finally (putMVar done ())
-        $ writeStds parentStdoutRead parentStderrRead refOut refErr
-
-      status <- SPPB.getProcessStatus True True pid
-      void $ race (takeMVar done) (threadDelay (1000000 * 3))
-
-      case status of
-        -- readFd will take care of closing the fd
-        Just (SPPB.Exited es) -> do
-          stdout' <- readIORef refOut
-          stderr' <- readIORef refErr
-          pure $ CapturedProcess { _exitCode = es
-                                 , _stdOut   = stdout'
-                                 , _stdErr   = stderr'
-                                 }
-
-        _ -> throwIO $ userError ("No such PID " ++ show pid)
-
- where
-  writeStds pout perr rout rerr = do
-    doneOut <- newEmptyMVar
-    void
-      $ forkIO
-      $ hideError eofErrorType
-      $ flip EX.finally (putMVar doneOut ())
-      $ readTilEOF (\x -> modifyIORef' rout (<> x)) pout
-    doneErr <- newEmptyMVar
-    void
-      $ forkIO
-      $ hideError eofErrorType
-      $ flip EX.finally (putMVar doneErr ())
-      $ readTilEOF (\x -> modifyIORef' rerr (<> x)) perr
-    takeMVar doneOut
-    takeMVar doneErr
-
-  readTilEOF ~action' fd' = do
-    bs <- SPIB.fdRead fd' 512
-    void $ action' bs
-    readTilEOF action' fd'
-
-
-actionWithPipes :: ((Fd, Fd) -> IO b) -> IO b
-actionWithPipes a =
-  createPipe >>= \(p1, p2) -> flip finally (cleanup [p1, p2]) $ a (p1, p2)
-
-cleanup :: [Fd] -> IO ()
-cleanup fds = for_ fds $ \fd -> handleIO (\_ -> pure ()) $ closeFd fd
-
-
-
--- | Create a new regular file in write-only mode. The file must not exist.
-createRegularFileFd :: FileMode -> Path b -> IO Fd
-createRegularFileFd fm dest =
-  FD.openFd (toFilePath dest) WriteOnly [oExcl] (Just fm)
-
-
--- | Thin wrapper around `executeFile`.
-exec :: ByteString       -- ^ thing to execute
-     -> Bool             -- ^ whether to search PATH for the thing
-     -> [ByteString]     -- ^ args for the thing
-     -> Maybe (Path Abs) -- ^ optionally chdir into this
-     -> Maybe [(ByteString, ByteString)] -- ^ optional environment
-     -> IO (Either ProcessError ())
-exec exe spath args chdir env = do
-  pid <- SPPB.forkProcess $ do
-    maybe (pure ()) (changeWorkingDirectory . toFilePath) chdir
-    SPPB.executeFile exe spath args env
-
-  fmap (toProcessError exe args) $ SPPB.getProcessStatus True True pid
-
-
-toProcessError :: ByteString
-               -> [ByteString]
-               -> Maybe ProcessStatus
-               -> Either ProcessError ()
-toProcessError exe args mps = case mps of
-  Just (SPPB.Exited (ExitFailure xi)) -> Left $ NonZeroExit xi exe args
-  Just (SPPB.Exited ExitSuccess    ) -> Right ()
-  Just (Terminated _ _             ) -> Left $ PTerminated exe args
-  Just (Stopped _                  ) -> Left $ PStopped exe args
-  Nothing                            -> Left $ NoSuchPid exe args
-
-
--- | Search for a file in the search paths.
---
--- Catches `PermissionDenied` and `NoSuchThing` and returns `Nothing`.
-searchPath :: [Path Abs] -> Path Rel -> IO (Maybe (Path Abs))
-searchPath paths needle = go paths
- where
-  go [] = pure Nothing
-  go (x : xs) =
-    hideErrorDefM [InappropriateType, PermissionDenied, NoSuchThing] (go xs)
-      $ do
-          dirStream <- openDirStream (toFilePath x)
-          S.findM (\(_, p) -> isMatch x p) (dirContentsStream dirStream)
-            >>= \case
-                  Just _  -> pure $ Just (x </> needle)
-                  Nothing -> go xs
-  isMatch basedir p = do
-    if p == toFilePath needle
-      then isExecutable (basedir </> needle)
-      else pure False
-
-
--- | Check wether a binary is shadowed by another one that comes before
--- it in PATH. Returns the path to said binary, if any.
-isShadowed :: Path Abs -> IO (Maybe (Path Abs))
-isShadowed p = do
-  let dir = dirname p
-  fn <- basename p
-  spaths <- catMaybes . fmap parseAbs <$> liftIO getSearchPath
-  if dir `elem` spaths
-  then do
-    let shadowPaths = takeWhile (/= dir) spaths
-    searchPath shadowPaths fn
-  else pure Nothing
-
-
--- | Check whether the binary is in PATH. This returns only `True`
--- if the directory containing the binary is part of PATH.
-isInPath :: Path Abs -> IO Bool
-isInPath p = do
-  let dir = dirname p
-  fn <- basename p
-  spaths <- catMaybes . fmap parseAbs <$> liftIO getSearchPath
-  if dir `elem` spaths
-  then isJust <$> searchPath [dir] fn
-  else pure False
-
-
-findFiles :: Path Abs -> Regex -> IO [Path Rel]
-findFiles path regex = do
-  dirStream <- openDirStream (toFilePath path)
-  f         <-
-    (fmap . fmap) snd
-    . S.toList
-    . S.filter (\(_, p) -> match regex p)
-    $ dirContentsStream dirStream
-  pure $ parseRel =<< f
-
-
-findFiles' :: Path Abs -> MP.Parsec Void Text () -> IO [Path Rel]
-findFiles' path parser = do
-  dirStream <- openDirStream (toFilePath path)
-  f         <-
-    (fmap . fmap) snd
-    . S.toList
-    . S.filter (\(_, p) -> case E.decodeUtf8' p of
-                             Left _ -> False
-                             Right p' -> isJust $ MP.parseMaybe parser p')
-    $ dirContentsStream dirStream
-  pure $ parseRel =<< f
-
-
-isBrokenSymlink :: Path Abs -> IO Bool
-isBrokenSymlink p =
-  handleIO
-      (\e -> if ioeGetErrorType e == NoSuchThing then pure True else throwIO e)
-    $ do
-        _ <- canonicalizePath p
-        pure False
-
-
-chmod_755 :: (MonadLogger m, MonadIO m) => Path a -> m ()
-chmod_755 (toFilePath -> fp) = do
-  let exe_mode =
-          nullFileMode
-            `unionFileModes` ownerExecuteMode
-            `unionFileModes` ownerReadMode
-            `unionFileModes` ownerWriteMode
-            `unionFileModes` groupExecuteMode
-            `unionFileModes` groupReadMode
-            `unionFileModes` otherExecuteMode
-            `unionFileModes` otherReadMode
-  $(logDebug) [i|chmod 755 #{fp}|]
-  liftIO $ setFileMode fp exe_mode
+{-# LANGUAGE CPP #-}
+
+module GHCup.Utils.File (
+  module GHCup.Utils.File.Common,
+#if IS_WINDOWS
+  module GHCup.Utils.File.Windows
+#else
+  module GHCup.Utils.File.Posix
+#endif
+) where
+
+import GHCup.Utils.File.Common
+#if IS_WINDOWS
+import GHCup.Utils.File.Windows
+#else
+import GHCup.Utils.File.Posix
+#endif
diff --git a/lib/GHCup/Utils/File/Common.hs b/lib/GHCup/Utils/File/Common.hs
new file mode 100644
index 0000000000000000000000000000000000000000..5b7918ec27af33981c9ef838466a70bd037a8532
--- /dev/null
+++ b/lib/GHCup/Utils/File/Common.hs
@@ -0,0 +1,161 @@
+{-# LANGUAGE QuasiQuotes       #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE FlexibleContexts  #-}
+{-# LANGUAGE TemplateHaskell   #-}
+{-# LANGUAGE ViewPatterns      #-}
+
+module GHCup.Utils.File.Common where
+
+import           GHCup.Utils.Prelude
+import           GHCup.Types
+
+import           Control.Concurrent
+import           Control.Concurrent.Async
+import           Control.DeepSeq
+import           Control.Exception              ( evaluate )
+import           Control.Exception.Safe
+import           Control.Monad
+import           Control.Monad.Logger
+import           Control.Monad.Reader
+import           Control.Monad.Trans.State.Strict
+import           Data.ByteString                ( ByteString )
+import           Data.Foldable
+import           Data.Functor
+import           Data.IORef
+import           Data.Maybe
+import           Data.Sequence                  ( Seq, (|>) )
+import           Data.String.Interpolate
+import           Data.Text                      ( Text )
+import           Data.Void
+import           Data.Word8
+import           Foreign.C.Error
+import           GHC.IO.Exception
+import           GHC.IO.Handle
+import           Optics                  hiding ((<|), (|>))
+import           System.Console.Pretty   hiding ( Pretty )
+import           System.Console.Regions
+import           System.IO
+import           System.IO.Error
+import           System.Process
+import           System.Posix.Types
+import           Text.PrettyPrint.HughesPJClass hiding ( (<>) )
+import           Text.Regex.Posix
+
+
+import System.Directory
+import System.FilePath
+import Control.Monad.Extra
+
+
+import qualified Control.Exception             as EX
+import qualified Data.Sequence                 as Sq
+import qualified Data.Text                     as T
+import qualified Data.Text.Encoding            as E
+import           Streamly.External.Posix.DirStream
+import qualified Streamly.Prelude              as S
+import qualified Text.Megaparsec               as MP
+import qualified Data.ByteString               as BS
+import qualified Data.ByteString.Lazy          as BL
+
+
+
+
+
+
+
+data ProcessError = NonZeroExit Int FilePath [String]
+                  | PTerminated FilePath [String]
+                  | PStopped FilePath [String]
+                  | NoSuchPid FilePath [String]
+                  deriving Show
+
+instance Pretty ProcessError where
+  pPrint (NonZeroExit e exe args) =
+    text [i|Process "#{exe}" with arguments #{args} failed with exit code #{e}.|]
+  pPrint (PTerminated exe args) =
+    text [i|Process "#{exe}" with arguments #{args} terminated.|]
+  pPrint (PStopped exe args) =
+    text [i|Process "#{exe}" with arguments #{args} stopped.|]
+  pPrint (NoSuchPid exe args) =
+    text [i|Could not find PID for process running "#{exe}" with arguments #{args}.|]
+
+data CapturedProcess = CapturedProcess
+  { _exitCode :: ExitCode
+  , _stdOut   :: BL.ByteString
+  , _stdErr   :: BL.ByteString
+  }
+  deriving (Eq, Show)
+
+makeLenses ''CapturedProcess
+
+
+
+-- | Search for a file in the search paths.
+--
+-- Catches `PermissionDenied` and `NoSuchThing` and returns `Nothing`.
+searchPath :: [FilePath] -> FilePath -> IO (Maybe FilePath)
+searchPath paths needle = go paths
+ where
+  go [] = pure Nothing
+  go (x : xs) =
+    hideErrorDefM [InappropriateType, PermissionDenied, NoSuchThing] (go xs)
+      $ do
+          contents <- listDirectory x
+          findM (isMatch x) contents >>= \case
+            Just _ -> pure $ Just (x </> needle)
+            Nothing -> go xs
+  isMatch basedir p = do
+    if p == needle
+      then isExecutable (basedir </> needle)
+      else pure False
+
+  isExecutable :: FilePath -> IO Bool
+  isExecutable file = executable <$> getPermissions file
+
+
+-- | Check wether a binary is shadowed by another one that comes before
+-- it in PATH. Returns the path to said binary, if any.
+isShadowed :: FilePath -> IO (Maybe FilePath)
+isShadowed p = do
+  let dir = takeDirectory p
+  let fn = takeFileName p
+  spaths <- liftIO getSearchPath
+  if dir `elem` spaths
+  then do
+    let shadowPaths = takeWhile (/= dir) spaths
+    searchPath shadowPaths fn
+  else pure Nothing
+
+
+-- | Check whether the binary is in PATH. This returns only `True`
+-- if the directory containing the binary is part of PATH.
+isInPath :: FilePath -> IO Bool
+isInPath p = do
+  let dir = takeDirectory p
+  let fn = takeFileName p
+  spaths <- liftIO getSearchPath
+  if dir `elem` spaths
+  then isJust <$> searchPath [dir] fn
+  else pure False
+
+
+findFiles :: FilePath -> Regex -> IO [FilePath]
+findFiles path regex = do
+  contents <- listDirectory path
+  pure $ filter (match regex) contents
+
+
+findFiles' :: FilePath -> MP.Parsec Void Text () -> IO [FilePath]
+findFiles' path parser = do
+  contents <- listDirectory path
+  pure $ fmap T.unpack $ filter (isJust . MP.parseMaybe parser) (fmap T.pack contents)
+
+
+isBrokenSymlink :: FilePath -> IO Bool
+isBrokenSymlink p =
+  handleIO
+      (\e -> if ioeGetErrorType e == NoSuchThing then pure True else throwIO e)
+    $ do
+        _ <- canonicalizePath p
+        pure False
+
diff --git a/lib/GHCup/Utils/File/Posix.hs b/lib/GHCup/Utils/File/Posix.hs
new file mode 100644
index 0000000000000000000000000000000000000000..8dfb1848d3c1da6ba0b9b79742799021f2145db8
--- /dev/null
+++ b/lib/GHCup/Utils/File/Posix.hs
@@ -0,0 +1,379 @@
+{-# LANGUAGE QuasiQuotes       #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE FlexibleContexts  #-}
+{-# LANGUAGE TemplateHaskell   #-}
+{-# LANGUAGE ViewPatterns      #-}
+
+{-|
+Module      : GHCup.Utils.File.Posix
+Description : File and unix APIs
+Copyright   : (c) Julian Ospald, 2020
+License     : LGPL-3.0
+Maintainer  : hasufell@hasufell.de
+Stability   : experimental
+Portability : POSIX
+
+This module handles file and executable handling.
+Some of these functions use sophisticated logging.
+-}
+module GHCup.Utils.File.Posix where
+
+import           GHCup.Utils.File.Common
+import           GHCup.Utils.Prelude
+import           GHCup.Types
+
+import           Control.Concurrent
+import           Control.Concurrent.Async
+import           Control.Exception              ( evaluate )
+import           Control.Exception.Safe
+import           Control.Monad
+import           Control.Monad.Logger
+import           Control.Monad.Reader
+import           Control.Monad.Trans.State.Strict
+import           Data.ByteString                ( ByteString )
+import           Data.Foldable
+import           Data.Functor
+import           Data.IORef
+import           Data.Maybe
+import           Data.Sequence                  ( Seq, (|>) )
+import           Data.String.Interpolate
+import           Data.List
+import           Data.Text                      ( Text )
+import           Data.Void
+import           Data.Word8
+import           GHC.IO.Exception
+import           Optics                  hiding ((<|), (|>))
+import           System.Console.Pretty   hiding ( Pretty )
+import           System.Console.Regions
+import           System.IO.Error
+import           System.FilePath
+import           System.Posix.Directory
+import           System.Posix.Files
+import           System.Posix.Foreign           ( oExcl, oAppend )
+import           System.Posix.IO
+import           System.Posix.Process           ( ProcessStatus(..) )
+import           System.Posix.Types
+import           Text.PrettyPrint.HughesPJClass hiding ( (<>) )
+import           Text.Regex.Posix
+
+
+import qualified Control.Exception             as EX
+import qualified Data.Sequence                 as Sq
+import qualified Data.Text                     as T
+import qualified Data.Text.Encoding            as E
+import qualified System.Posix.Process          as SPP
+import           Streamly.External.Posix.DirStream
+import qualified Streamly.Prelude              as S
+import qualified Text.Megaparsec               as MP
+import qualified Data.ByteString               as BS
+import qualified Data.ByteString.Lazy          as BL
+import qualified "unix-bytestring" System.Posix.IO.ByteString
+                                               as SPIB
+
+
+
+-- | Execute the given command and collect the stdout, stderr and the exit code.
+-- The command is run in a subprocess.
+executeOut :: FilePath          -- ^ command as filename, e.g. 'ls'
+           -> [String]          -- ^ arguments to the command
+           -> Maybe FilePath    -- ^ chdir to this path
+           -> IO CapturedProcess
+executeOut path args chdir = captureOutStreams $ do
+  maybe (pure ()) changeWorkingDirectory chdir
+  SPP.executeFile path True args Nothing
+
+
+execLogged :: (MonadReader AppState m, MonadIO m, MonadThrow m)
+           => FilePath         -- ^ thing to execute
+           -> [String]         -- ^ args for the thing
+           -> Maybe FilePath   -- ^ optionally chdir into this
+           -> FilePath         -- ^ log filename (opened in append mode)
+           -> Maybe [(String, String)] -- ^ optional environment
+           -> m (Either ProcessError ())
+execLogged exe args chdir lfile env = do
+  AppState { settings = Settings {..}, dirs = Dirs {..} } <- ask
+  let logfile = logsDir </> lfile <> ".log"
+  liftIO $ bracket (openFd logfile WriteOnly (Just newFilePerms) defaultFileFlags{ append = True })
+                   closeFd
+                   (action verbose)
+ where
+  action verbose fd = do
+    actionWithPipes $ \(stdoutRead, stdoutWrite) -> do
+      -- start the thread that logs to stdout
+      pState <- newEmptyMVar
+      done   <- newEmptyMVar
+      void
+        $ forkIO
+        $ EX.handle (\(_ :: IOException) -> pure ())
+        $ EX.finally
+            (if verbose
+              then tee fd stdoutRead
+              else printToRegion fd stdoutRead 6 pState
+            )
+            (putMVar done ())
+
+      -- fork the subprocess
+      pid <- SPP.forkProcess $ do
+        void $ dupTo stdoutWrite stdOutput
+        void $ dupTo stdoutWrite stdError
+        closeFd stdoutRead
+        closeFd stdoutWrite
+
+        -- execute the action
+        maybe (pure ()) changeWorkingDirectory chdir
+        void $ SPP.executeFile exe (if "./" `isPrefixOf` exe then False else True) args env
+
+      closeFd stdoutWrite
+
+      -- wait for the subprocess to finish
+      e <- toProcessError exe args <$!> SPP.getProcessStatus True True pid
+      putMVar pState (either (const False) (const True) e)
+
+      void $ race (takeMVar done) (threadDelay (1000000 * 3))
+      closeFd stdoutRead
+
+      pure e
+
+  tee :: Fd -> Fd -> IO ()
+  tee fileFd fdIn = readTilEOF lineAction fdIn
+
+   where
+    lineAction :: ByteString -> IO ()
+    lineAction bs' = do
+      void $ SPIB.fdWrite fileFd (bs' <> "\n")
+      void $ SPIB.fdWrite stdOutput (bs' <> "\n")
+
+  -- Reads fdIn and logs the output in a continous scrolling area
+  -- of 'size' terminal lines. Also writes to a log file.
+  printToRegion :: Fd -> Fd -> Int -> MVar Bool -> IO ()
+  printToRegion fileFd fdIn size pState = do
+    void $ displayConsoleRegions $ do
+      rs <-
+        liftIO
+        . fmap Sq.fromList
+        . sequence
+        . replicate size
+        . openConsoleRegion
+        $ Linear
+      flip runStateT mempty
+        $ handle
+            (\(ex :: SomeException) -> do
+              ps <- liftIO $ takeMVar pState
+              when ps (forM_ rs (liftIO . closeConsoleRegion))
+              throw ex
+            )
+        $ readTilEOF (lineAction rs) fdIn
+
+   where
+    -- action to perform line by line
+    -- TODO: do this with vty for efficiency
+    lineAction :: (MonadMask m, MonadIO m)
+               => Seq ConsoleRegion
+               -> ByteString
+               -> StateT (Seq ByteString) m ()
+    lineAction rs = \bs' -> do
+      void $ liftIO $ SPIB.fdWrite fileFd (bs' <> "\n")
+      modify (swapRegs bs')
+      regs <- get
+      liftIO $ forM_ (Sq.zip regs rs) $ \(bs, r) -> setConsoleRegion r $ do
+        w <- consoleWidth
+        return
+          . T.pack
+          . color Blue
+          . T.unpack
+          . decUTF8Safe
+          . trim w
+          . (\b -> "[ " <> E.encodeUtf8 (T.pack lfile) <> " ] " <> b)
+          $ bs
+
+    swapRegs :: a -> Seq a -> Seq a
+    swapRegs bs = \regs -> if
+      | Sq.length regs < size -> regs |> bs
+      | otherwise             -> Sq.drop 1 regs |> bs
+
+    -- trim output line to terminal width
+    trim :: Int -> ByteString -> ByteString
+    trim w = \bs -> if
+      | BS.length bs > w && w > 5 -> BS.take (w - 4) bs <> "..."
+      | otherwise                 -> bs
+
+  -- Consecutively read from Fd in 512 chunks until we hit
+  -- newline or EOF.
+  readLine :: MonadIO m
+           => Fd          -- ^ input file descriptor
+           -> ByteString  -- ^ rest buffer (read across newline)
+           -> m (ByteString, ByteString, Bool) -- ^ (full line, rest, eof)
+  readLine fd = go
+   where
+    go inBs = do
+      -- if buffer is not empty, process it first
+      mbs <- if BS.length inBs == 0
+               -- otherwise attempt read
+               then liftIO
+                    $ handleIO (\e -> if isEOFError e then pure Nothing else ioError e)
+                    $ fmap Just
+                    $ SPIB.fdRead fd 512
+               else pure $ Just inBs
+      case mbs of
+        Nothing -> pure ("", "", True)
+        Just bs -> do
+          -- split on newline
+          let (line, rest) = BS.span (/= _lf) bs
+          if
+            | BS.length rest /= 0 -> pure (line, BS.tail rest, False)
+            -- if rest is empty, then there was no newline, process further
+            | otherwise           -> (\(l, r, b) -> (line <> l, r, b)) <$!> go mempty
+
+  readTilEOF :: MonadIO m => (ByteString -> m a) -> Fd -> m ()
+  readTilEOF ~action' fd' = go mempty
+   where
+    go bs' = do
+      (bs, rest, eof) <- readLine fd' bs'
+      if eof
+         then liftIO $ ioError (mkIOError eofErrorType "" Nothing Nothing)
+         else void (action' bs) >> go rest
+
+
+-- | Capture the stdout and stderr of the given action, which
+-- is run in a subprocess. Stdin is closed. You might want to
+-- 'race' this to make sure it terminates.
+captureOutStreams :: IO a
+                     -- ^ the action to execute in a subprocess
+                  -> IO CapturedProcess
+captureOutStreams action = do
+  actionWithPipes $ \(parentStdoutRead, childStdoutWrite) ->
+    actionWithPipes $ \(parentStderrRead, childStderrWrite) -> do
+      pid <- SPP.forkProcess $ do
+        -- dup stdout
+        void $ dupTo childStdoutWrite stdOutput
+        closeFd childStdoutWrite
+        closeFd parentStdoutRead
+
+        -- dup stderr
+        void $ dupTo childStderrWrite stdError
+        closeFd childStderrWrite
+        closeFd parentStderrRead
+
+        -- execute the action
+        a <- action
+        void $ evaluate a
+
+      -- close everything we don't need
+      closeFd childStdoutWrite
+      closeFd childStderrWrite
+
+      -- start thread that writes the output
+      refOut <- newIORef BL.empty
+      refErr <- newIORef BL.empty
+      done   <- newEmptyMVar
+      _      <-
+        forkIO
+        $ EX.handle (\(_ :: IOException) -> pure ())
+        $ flip EX.finally (putMVar done ())
+        $ writeStds parentStdoutRead parentStderrRead refOut refErr
+
+      status <- SPP.getProcessStatus True True pid
+      void $ race (takeMVar done) (threadDelay (1000000 * 3))
+
+      case status of
+        -- readFd will take care of closing the fd
+        Just (SPP.Exited es) -> do
+          stdout' <- readIORef refOut
+          stderr' <- readIORef refErr
+          pure $ CapturedProcess { _exitCode = es
+                                 , _stdOut   = stdout'
+                                 , _stdErr   = stderr'
+                                 }
+
+        _ -> throwIO $ userError ("No such PID " ++ show pid)
+
+ where
+  writeStds :: Fd -> Fd -> IORef BL.ByteString -> IORef BL.ByteString -> IO ()
+  writeStds pout perr rout rerr = do
+    doneOut <- newEmptyMVar
+    void
+      $ forkIO
+      $ hideError eofErrorType
+      $ flip EX.finally (putMVar doneOut ())
+      $ readTilEOF (\x -> modifyIORef' rout (<> BL.fromStrict x)) pout
+    doneErr <- newEmptyMVar
+    void
+      $ forkIO
+      $ hideError eofErrorType
+      $ flip EX.finally (putMVar doneErr ())
+      $ readTilEOF (\x -> modifyIORef' rerr (<> BL.fromStrict x)) perr
+    takeMVar doneOut
+    takeMVar doneErr
+
+  readTilEOF ~action' fd' = do
+    bs <- SPIB.fdRead fd' 512
+    void $ action' bs
+    readTilEOF action' fd'
+
+
+actionWithPipes :: ((Fd, Fd) -> IO b) -> IO b
+actionWithPipes a =
+  createPipe >>= \(p1, p2) -> flip finally (cleanup [p1, p2]) $ a (p1, p2)
+
+cleanup :: [Fd] -> IO ()
+cleanup fds = for_ fds $ \fd -> handleIO (\_ -> pure ()) $ closeFd fd
+
+
+
+-- | Create a new regular file in write-only mode. The file must not exist.
+createRegularFileFd :: FileMode -> FilePath -> IO Fd
+createRegularFileFd fm dest =
+  openFd dest WriteOnly (Just fm) defaultFileFlags{ exclusive = True }
+
+
+-- | Thin wrapper around `executeFile`.
+exec :: String           -- ^ thing to execute
+     -> [String]         -- ^ args for the thing
+     -> Maybe FilePath   -- ^ optionally chdir into this
+     -> Maybe [(String, String)] -- ^ optional environment
+     -> IO (Either ProcessError ())
+exec exe args chdir env = do
+  pid <- SPP.forkProcess $ do
+    maybe (pure ()) changeWorkingDirectory chdir
+    SPP.executeFile exe (if "./" `isPrefixOf` exe then False else True) args env
+
+  fmap (toProcessError exe args) $ SPP.getProcessStatus True True pid
+
+
+toProcessError :: FilePath
+               -> [String]
+               -> Maybe ProcessStatus
+               -> Either ProcessError ()
+toProcessError exe args mps = case mps of
+  Just (SPP.Exited (ExitFailure xi)) -> Left $ NonZeroExit xi exe args
+  Just (SPP.Exited ExitSuccess    ) -> Right ()
+  Just (Terminated _ _             ) -> Left $ PTerminated exe args
+  Just (Stopped _                  ) -> Left $ PStopped exe args
+  Nothing                            -> Left $ NoSuchPid exe args
+
+
+
+chmod_755 :: (MonadLogger m, MonadIO m) => FilePath -> m ()
+chmod_755 fp = do
+  let exe_mode =
+          nullFileMode
+            `unionFileModes` ownerExecuteMode
+            `unionFileModes` ownerReadMode
+            `unionFileModes` ownerWriteMode
+            `unionFileModes` groupExecuteMode
+            `unionFileModes` groupReadMode
+            `unionFileModes` otherExecuteMode
+            `unionFileModes` otherReadMode
+  $(logDebug) [i|chmod 755 #{fp}|]
+  liftIO $ setFileMode fp exe_mode
+
+
+-- |Default permissions for a new file.
+newFilePerms :: FileMode
+newFilePerms =
+  ownerWriteMode
+    `unionFileModes` ownerReadMode
+    `unionFileModes` groupWriteMode
+    `unionFileModes` groupReadMode
+    `unionFileModes` otherWriteMode
+    `unionFileModes` otherReadMode
diff --git a/lib/GHCup/Utils/File/Windows.hs b/lib/GHCup/Utils/File/Windows.hs
new file mode 100644
index 0000000000000000000000000000000000000000..618239f168320b37fdb8604a82d41c27965c2a52
--- /dev/null
+++ b/lib/GHCup/Utils/File/Windows.hs
@@ -0,0 +1,225 @@
+{-# LANGUAGE QuasiQuotes       #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE FlexibleContexts  #-}
+{-# LANGUAGE TemplateHaskell   #-}
+{-# LANGUAGE ViewPatterns      #-}
+
+{-|
+Module      : GHCup.Utils.File.Windows
+Description : File and windows APIs
+Copyright   : (c) Julian Ospald, 2020
+License     : LGPL-3.0
+Maintainer  : hasufell@hasufell.de
+Stability   : experimental
+Portability : Windows
+
+This module handles file and executable handling.
+Some of these functions use sophisticated logging.
+-}
+module GHCup.Utils.File.Windows where
+
+import           GHCup.Utils.File.Common
+import           GHCup.Utils.Prelude
+import           GHCup.Types
+
+import           Control.Concurrent
+import           Control.Concurrent.Async
+import           Control.DeepSeq
+import           Control.Exception              ( evaluate )
+import           Control.Exception.Safe
+import           Control.Monad
+import           Control.Monad.Logger
+import           Control.Monad.Reader
+import           Control.Monad.Trans.State.Strict
+import           Data.ByteString                ( ByteString )
+import           Data.Foldable
+import           Data.Functor
+import           Data.IORef
+import           Data.Maybe
+import           Data.Sequence                  ( Seq, (|>) )
+import           Data.String.Interpolate
+import           Data.Text                      ( Text )
+import           Data.Void
+import           Data.Word8
+import           Foreign.C.Error
+import           GHC.IO.Exception
+import           GHC.IO.Handle
+import           Optics                  hiding ((<|), (|>))
+import           System.Console.Pretty   hiding ( Pretty )
+import           System.Console.Regions
+import           System.IO
+import           System.IO.Error
+import           System.Process
+import           System.Posix.Types
+import           Text.PrettyPrint.HughesPJClass hiding ( (<>) )
+import           Text.Regex.Posix
+
+
+import System.Directory
+import System.FilePath.Windows
+import Control.Monad.Extra
+
+
+import qualified Control.Exception             as EX
+import qualified Data.Sequence                 as Sq
+import qualified Data.Text                     as T
+import qualified Data.Text.Encoding            as E
+import           Streamly.External.Posix.DirStream
+import qualified Streamly.Prelude              as S
+import qualified Text.Megaparsec               as MP
+import qualified Data.ByteString               as BS
+import qualified Data.ByteString.Lazy          as BL
+
+
+
+toProcessError :: FilePath
+               -> [FilePath]
+               -> ExitCode
+               -> Either ProcessError ()
+toProcessError exe args exitcode = case exitcode of
+  (ExitFailure xi) -> Left $ NonZeroExit xi exe args
+  ExitSuccess -> Right ()
+
+
+-- | @readCreateProcessWithExitCode@ works exactly like 'readProcessWithExitCode' except that it
+-- lets you pass 'CreateProcess' giving better flexibility.
+--
+-- Note that @Handle@s provided for @std_in@, @std_out@, or @std_err@ via the CreateProcess
+-- record will be ignored.
+--
+-- @since 1.2.3.0
+readCreateProcessWithExitCodeBS
+    :: CreateProcess
+    -> BL.ByteString                               -- ^ standard input
+    -> IO (ExitCode, BL.ByteString, BL.ByteString) -- ^ exitcode, stdout, stderr
+readCreateProcessWithExitCodeBS cp input = do
+    let cp_opts = cp {
+                    std_in  = CreatePipe,
+                    std_out = CreatePipe,
+                    std_err = CreatePipe
+                  }
+    withCreateProcess_ "readCreateProcessWithExitCode" cp_opts $
+      \mb_inh mb_outh mb_errh ph ->
+        case (mb_inh, mb_outh, mb_errh) of
+          (Just inh, Just outh, Just errh) -> do
+
+            out <- BL.hGetContents outh
+            err <- BL.hGetContents errh
+
+            -- fork off threads to start consuming stdout & stderr
+            withForkWait  (EX.evaluate $ rnf out) $ \waitOut ->
+             withForkWait (EX.evaluate $ rnf err) $ \waitErr -> do
+
+              -- now write any input
+              unless (BL.null input) $
+                ignoreSigPipe $ BL.hPut inh input
+              -- hClose performs implicit hFlush, and thus may trigger a SIGPIPE
+              ignoreSigPipe $ hClose inh
+
+              -- wait on the output
+              waitOut
+              waitErr
+
+              hClose outh
+              hClose errh
+
+            -- wait on the process
+            ex <- waitForProcess ph
+            return (ex, out, err)
+
+          (Nothing,_,_) -> error "readCreateProcessWithExitCodeBS: Failed to get a stdin handle."
+          (_,Nothing,_) -> error "readCreateProcessWithExitCodeBS: Failed to get a stdout handle."
+          (_,_,Nothing) -> error "readCreateProcessWithExitCodeBS: Failed to get a stderr handle."
+ where
+  ignoreSigPipe :: IO () -> IO ()
+  ignoreSigPipe = EX.handle $ \e -> case e of
+                                     IOError { ioe_type  = ResourceVanished
+                                             , ioe_errno = Just ioe }
+                                       | Errno ioe == ePIPE -> return ()
+                                     _ -> throwIO e
+  -- wrapper so we can get exceptions with the appropriate function name.
+  withCreateProcess_
+    :: String
+    -> CreateProcess
+    -> (Maybe Handle -> Maybe Handle -> Maybe Handle -> ProcessHandle -> IO a)
+    -> IO a
+  withCreateProcess_ fun c action =
+      EX.bracketOnError (createProcess_ fun c) cleanupProcess
+                       (\(m_in, m_out, m_err, ph) -> action m_in m_out m_err ph)
+
+-- | Fork a thread while doing something else, but kill it if there's an
+-- exception.
+--
+-- This is important in the cases above because we want to kill the thread
+-- that is holding the Handle lock, because when we clean up the process we
+-- try to close that handle, which could otherwise deadlock.
+--
+withForkWait :: IO () -> (IO () ->  IO a) -> IO a
+withForkWait async body = do
+  waitVar <- newEmptyMVar :: IO (MVar (Either SomeException ()))
+  mask $ \restore -> do
+    tid <- forkIO $ try (restore async) >>= putMVar waitVar
+    let wait = takeMVar waitVar >>= either throwIO return
+    restore (body wait) `EX.onException` killThread tid
+
+
+-- | Execute the given command and collect the stdout, stderr and the exit code.
+-- The command is run in a subprocess.
+executeOut :: FilePath          -- ^ command as filename, e.g. 'ls'
+           -> [String]          -- ^ arguments to the command
+           -> Maybe FilePath    -- ^ chdir to this path
+           -> IO CapturedProcess
+executeOut path args chdir = do
+  (exit, out, err) <- readCreateProcessWithExitCodeBS (proc path args){ cwd = chdir, std_in = NoStream } ""
+  pure $ CapturedProcess exit out err
+
+
+execLogged :: (MonadReader AppState m, MonadIO m, MonadThrow m)
+           => FilePath         -- ^ thing to execute
+           -> [String]         -- ^ args for the thing
+           -> Maybe FilePath   -- ^ optionally chdir into this
+           -> FilePath         -- ^ log filename (opened in append mode)
+           -> Maybe [(String, String)] -- ^ optional environment
+           -> m (Either ProcessError ())
+execLogged exe args chdir lfile env = do
+  AppState { settings = Settings {..}, dirs = Dirs {..} } <- ask
+  let stdoutLogfile = logsDir </> lfile <> ".stdout.log"
+      stderrLogfile = logsDir </> lfile <> ".stderr.log"
+  fmap (toProcessError exe args)
+    $ liftIO
+    $ withCreateProcess
+        (proc exe args){ cwd = chdir, env = env, std_in = NoStream }
+    $ \_ (Just cStdout) (Just cStderr) ph -> do
+        withForkWait (tee stdoutLogfile cStdout) $ \waitOut ->
+          withForkWait (tee stderrLogfile cStderr) $ \waitErr -> do
+            waitOut
+            waitErr
+        waitForProcess ph
+
+ where
+  tee :: FilePath -> Handle -> IO ()
+  tee logFile handle = do
+    some <- BS.hGetSome handle 512
+    if BS.null some
+      then pure ()
+      else do
+        void $ BS.appendFile logFile some
+        void $ BS.hPut stdout some
+        tee logFile handle
+        
+
+-- | Thin wrapper around `executeFile`.
+exec :: FilePath       -- ^ thing to execute
+     -> [FilePath]     -- ^ args for the thing
+     -> Maybe FilePath   -- ^ optionally chdir into this
+     -> Maybe [(String, String)] -- ^ optional environment
+     -> IO (Either ProcessError ())
+exec exe args chdir env = do
+  (exit, out, err) <- readCreateProcessWithExitCodeBS (proc exe args){ cwd = chdir, std_in = NoStream, env = env } ""
+  pure $ toProcessError exe args exit
+
+
+chmod_755 :: MonadIO m => FilePath -> m ()
+chmod_755 fp =
+  let perm = setOwnerWritable True emptyPermissions
+  in liftIO $ setPermissions fp perm
diff --git a/lib/GHCup/Utils/Logger.hs b/lib/GHCup/Utils/Logger.hs
index 5f84c397c1d7b65e9d131b4b8a6f73964026796d..1c5555a1139c1c51a2c360b176a9dbfeaf2b656a 100644
--- a/lib/GHCup/Utils/Logger.hs
+++ b/lib/GHCup/Utils/Logger.hs
@@ -8,14 +8,13 @@ Copyright   : (c) Julian Ospald, 2020
 License     : LGPL-3.0
 Maintainer  : hasufell@hasufell.de
 Stability   : experimental
-Portability : POSIX
+Portability : portable
 
 Here we define our main logger.
 -}
 module GHCup.Utils.Logger where
 
 import           GHCup.Types
-import           GHCup.Utils
 import           GHCup.Utils.File
 import           GHCup.Utils.String.QQ
 
@@ -23,14 +22,15 @@ import           Control.Monad
 import           Control.Monad.IO.Class
 import           Control.Monad.Reader
 import           Control.Monad.Logger
-import           HPath
-import           HPath.IO
 import           Prelude                 hiding ( appendFile )
 import           System.Console.Pretty
+import           System.Directory        hiding ( findFiles )
+import           System.FilePath
 import           System.IO.Error
 import           Text.Regex.Posix
 
 import qualified Data.ByteString               as B
+import GHCup.Utils.Prelude
 
 
 data LoggerConfig = LoggerConfig
@@ -68,19 +68,19 @@ myLoggerT LoggerConfig {..} loggingt = runLoggingT loggingt mylogger
     rawOutter outr
 
 
-initGHCupFileLogging :: (MonadIO m, MonadReader AppState m) => m (Path Abs)
+initGHCupFileLogging :: (MonadIO m, MonadReader AppState m) => m FilePath
 initGHCupFileLogging = do
   AppState {dirs = Dirs {..}} <- ask
-  let logfile = logsDir </> [rel|ghcup.log|]
+  let logfile = logsDir </> "ghcup.log"
   liftIO $ do
-    createDirRecursive' logsDir
+    createDirectoryIfMissing True logsDir
     logFiles <- findFiles
       logsDir
       (makeRegexOpts compExtended
                      execBlank
                      ([s|^.*\.log$|] :: B.ByteString)
       )
-    forM_ logFiles $ hideError doesNotExistErrorType . deleteFile . (logsDir </>)
+    forM_ logFiles $ hideError doesNotExistErrorType . removeFile . (logsDir </>)
 
-    createRegularFile newFilePerms logfile
+    writeFile logfile ""
     pure logfile
diff --git a/lib/GHCup/Utils/MegaParsec.hs b/lib/GHCup/Utils/MegaParsec.hs
index c92762cc0f329e9a038c3ecd629d0111d2996c46..94e2d7eba219c9a1948a34db837ea5ac29f4eec0 100644
--- a/lib/GHCup/Utils/MegaParsec.hs
+++ b/lib/GHCup/Utils/MegaParsec.hs
@@ -8,7 +8,7 @@ Copyright   : (c) Julian Ospald, 2020
 License     : LGPL-3.0
 Maintainer  : hasufell@hasufell.de
 Stability   : experimental
-Portability : POSIX
+Portability : portable
 -}
 module GHCup.Utils.MegaParsec where
 
diff --git a/lib/GHCup/Utils/Prelude.hs b/lib/GHCup/Utils/Prelude.hs
index a270e20de3e798efe6116f6ef68aefe3a41a5654..47ad7ab9cf4c9681f7228099759c10f273151e52 100644
--- a/lib/GHCup/Utils/Prelude.hs
+++ b/lib/GHCup/Utils/Prelude.hs
@@ -12,7 +12,7 @@ Copyright   : (c) Julian Ospald, 2020
 License     : LGPL-3.0
 Maintainer  : hasufell@hasufell.de
 Stability   : experimental
-Portability : POSIX
+Portability : portable
 
 GHCup specific prelude. Lots of Excepts functionality.
 -}
@@ -32,8 +32,6 @@ import           Data.Word8
 import           Haskus.Utils.Types.List
 import           Haskus.Utils.Variant.Excepts
 import           System.IO.Error
-import           System.Posix.Env.ByteString    ( getEnvironment )
-
 import qualified Data.ByteString               as B
 import qualified Data.ByteString.Lazy          as L
 import qualified Data.Strict.Maybe             as S
@@ -242,6 +240,8 @@ throwEither' e eth = case eth of
 verToBS :: Version -> ByteString
 verToBS = E.encodeUtf8 . prettyVer
 
+verToS :: Version -> String
+verToS = T.unpack . prettyVer
 
 intToText :: Integral a => a -> T.Text
 intToText = TL.toStrict . B.toLazyText . B.decimal
@@ -252,14 +252,6 @@ removeLensFieldLabel str' =
   maybe str' T.unpack . T.stripPrefix (T.pack "_") . T.pack $ str'
 
 
-addToCurrentEnv :: MonadIO m
-                => [(ByteString, ByteString)]
-                -> m [(ByteString, ByteString)]
-addToCurrentEnv adds = do
-  cEnv <- liftIO getEnvironment
-  pure (adds ++ cEnv)
-
-
 pvpToVersion :: PVP -> Version
 pvpToVersion =
   either (\_ -> error "Couldn't convert PVP to Version") id
diff --git a/lib/GHCup/Utils/String/QQ.hs b/lib/GHCup/Utils/String/QQ.hs
index 85f566f7ce1ad877605cca396e9893f4ffe8694c..a47bb410de6b325cafb5af0d827b7a606a18786c 100644
--- a/lib/GHCup/Utils/String/QQ.hs
+++ b/lib/GHCup/Utils/String/QQ.hs
@@ -7,7 +7,7 @@ Copyright   : (c) Audrey Tang <audreyt@audreyt.org> 2019, Julian Ospald <hasufel
 License     : LGPL-3.0
 Maintainer  : hasufell@hasufell.de
 Stability   : experimental
-Portability : POSIX
+Portability : portable
 
 QuasiQuoter for non-interpolated strings, texts and bytestrings.
 
diff --git a/lib/GHCup/Utils/Version/QQ.hs b/lib/GHCup/Utils/Version/QQ.hs
index 9523db95ccd7db9974272d4807ff187c1021b6f7..e16d9a8cc9a08e7edd5a5428d4af84b5cae5ca7f 100644
--- a/lib/GHCup/Utils/Version/QQ.hs
+++ b/lib/GHCup/Utils/Version/QQ.hs
@@ -14,7 +14,7 @@ Copyright   : (c) Julian Ospald, 2020
 License     : LGPL-3.0
 Maintainer  : hasufell@hasufell.de
 Stability   : experimental
-Portability : POSIX
+Portability : portable
 -}
 module GHCup.Utils.Version.QQ where
 
diff --git a/lib/GHCup/Version.hs b/lib/GHCup/Version.hs
index 2cb8030f675cdfc2044f25a0cdf1893630b0b0b0..03944a579a893a346e2a07a0a1b5723fe224b38b 100644
--- a/lib/GHCup/Version.hs
+++ b/lib/GHCup/Version.hs
@@ -8,7 +8,7 @@ Copyright   : (c) Julian Ospald, 2020
 License     : LGPL-3.0
 Maintainer  : hasufell@hasufell.de
 Stability   : experimental
-Portability : POSIX
+Portability : portable
 -}
 module GHCup.Version where
 
diff --git a/stack.yaml b/stack.yaml
index c7a0937e6f325c0753d590e07cfd129b0d181522..9bf2d609602ed71f3323201b9492cc82cc93d8a9 100644
--- a/stack.yaml
+++ b/stack.yaml
@@ -7,6 +7,9 @@ extra-deps:
   - git: https://github.com/hasufell/text-conversions.git
     commit: 9abf0e5e5664a3178367597c32db19880477a53c
 
+  - git: https://github.com/hasufell/tar-bytestring
+    commit: 9b5970ca6c924069498e95a8b59cb21e909a9ebe
+
   - IfElse-0.85@sha256:6939b94acc6a55f545f63a168a349dd2fbe4b9a7cca73bf60282db5cc6aa47d2,445
   - ascii-string-1.0.1.4@sha256:fa34f1d9ba57e8e89c0d4c9cef5e01ba32cb2d4373d13f92dcc0b531a6c6749b,2582
   - brotli-0.0.0.0@sha256:2bf383a4cd308745740986be0b18381c5a0784393fe69b91456aacb2d603de46,2964
@@ -17,12 +20,9 @@ extra-deps:
   - haskus-utils-data-1.3@sha256:f62c4e49021b463185d043f7b69c727b63af641a71d7edd582d9f4f98e80e500,1466
   - haskus-utils-types-1.5.1@sha256:991c472f4e751e2f0d7aab6ad4220ef151d6160876dcf0511bbf876bbd432020,1298
   - haskus-utils-variant-3.0@sha256:8d51e45d3b664e61ccc25a58b37c0ccc4ee7537138b9fee21cd15c356906dd34,2159
-  - hpath-0.11.0@sha256:12b8405bee13d0007d644a888ef8407069ce7bbbd76970f8746b801447124ade,1440
-  - hpath-directory-0.14.1@sha256:548ac1321222c34caa843a41a2379a77d961141082a4695bb37cc4731e91b2c7,5312
+  - http-io-streams-0.1.6.0@sha256:53f5bab177efb52cd65ec396fd04ed59b93e5f919fb3700cd7dacd6cfce6f06d,3582
   - hpath-filepath-0.10.4@sha256:e9e44fb5fdbade7f30b5b5451257dbee15b6ef1aae4060034d73008bb3b5d878,1269
-  - hpath-io-0.14.1@sha256:d91373cd81483eb370a1c683e4add6182250dccce32f9b682bb1104f7765c750,1522
   - hpath-posix-0.13.2@sha256:eec4ff2b00dc86be847aca0f409fc8f6212ffd2170ec36a17dc9a52b46562392,1615
-  - http-io-streams-0.1.6.0@sha256:53f5bab177efb52cd65ec396fd04ed59b93e5f919fb3700cd7dacd6cfce6f06d,3582
   - lzma-static-5.2.5.2@sha256:ac38dcad9ab423342a72ba48415bd75f62234e9c9e11831495b75603b5a060f6,7184
   - libarchive-3.0.2.1@sha256:40ebf2a278e585802427bc58826867208bb33822f63d56107a1fcc3ca04d691d,10990
   - os-release-1.0.1@sha256:1281c62081f438fc3f0874d3bae6a4887d5964ac25261ba06e29d368ab173467,2716
@@ -36,13 +36,10 @@ extra-deps:
 flags:
   http-io-streams:
     brotli: false
-
   libarchive:
     system-libarchive: false
-
-  ghcup:
-    tui: true
-    internal-downloader: true
+  regex-posix:
+    _regex-posix-clib: true
 
 system-ghc: true
 compiler: ghc-8.10.4
diff --git a/test/GHCup/ArbitraryTypes.hs b/test/GHCup/ArbitraryTypes.hs
index e7179183e2ebbbacf334c7f138e07eec7719c9d4..1f536987f1ca72222a3338f381733a6f7254d966 100644
--- a/test/GHCup/ArbitraryTypes.hs
+++ b/test/GHCup/ArbitraryTypes.hs
@@ -11,7 +11,6 @@ import           GHCup.Types
 import           Data.ByteString                ( ByteString )
 import           Data.Versions
 import           Data.List.NonEmpty
-import           HPath
 import           Test.QuickCheck
 import           Test.QuickCheck.Arbitrary.ADT  ( ToADTArbitrary )
 import           Test.QuickCheck.Arbitrary.Generic
@@ -164,11 +163,6 @@ instance Arbitrary VersionCmp where
   arbitrary = genericArbitrary
   shrink    = genericShrink
 
-instance Arbitrary (Path Rel) where
-  arbitrary =
-    either (error . show) id . parseRel . E.encodeUtf8 . T.pack
-      <$> listOf1 (elements ['a' .. 'z'])
-
 instance Arbitrary TarDir where
   arbitrary = genericArbitrary
   shrink    = genericShrink