diff --git a/app/ghcup/BrickMain.hs b/app/ghcup/BrickMain.hs
index fe2bf2aa78b8ee75085c6f7b27d2693a98d3e352..41083102c7af74237bda3f9897b20f2def4618f8 100644
--- a/app/ghcup/BrickMain.hs
+++ b/app/ghcup/BrickMain.hs
@@ -437,6 +437,7 @@ install' _ (_, ListResult {..}) = do
               , TarDirDoesNotExist
               , FileAlreadyExistsError
               , ProcessError
+              , GHCupShadowed
               ]
 
   run (do
@@ -452,7 +453,7 @@ install' _ (_, ListResult {..}) = do
           liftE $ installCabalBin lVer Nothing False $> (vi, dirs, ce)
         GHCup -> do
           let vi = snd <$> getLatest dls GHCup
-          liftE $ upgradeGHCup Nothing False $> (vi, dirs, ce)
+          liftE $ upgradeGHCup Nothing False False $> (vi, dirs, ce)
         HLS   -> do
           let vi = getVersionInfo lVer HLS dls
           liftE $ installHLSBin lVer Nothing False $> (vi, dirs, ce)
diff --git a/app/ghcup/GHCup/OptParse.hs b/app/ghcup/GHCup/OptParse.hs
index 77fbb00c4c03df975db9a77d4cb4e400a490646a..9904c600114f26a8fec92e3ec309efd568ee4538 100644
--- a/app/ghcup/GHCup/OptParse.hs
+++ b/app/ghcup/GHCup/OptParse.hs
@@ -96,7 +96,7 @@ data Command
   | Config ConfigCommand
   | Whereis WhereisOptions WhereisCommand
 #ifndef DISABLE_UPGRADE
-  | Upgrade UpgradeOpts Bool
+  | Upgrade UpgradeOpts Bool Bool
 #endif
   | ToolRequirements ToolReqOpts
   | ChangeLog ChangeLogOptions
@@ -222,18 +222,18 @@ com =
            (info (List <$> listOpts <**> helper)
                  (progDesc "Show available GHCs and other tools")
            )
-#ifndef DISABLE_UPGRADE
       <> command
            "upgrade"
            (info
              (    (Upgrade <$> upgradeOptsP <*> switch
                     (short 'f' <> long "force" <> help "Force update")
+                    <*> switch
+                    (long "fail-if-shadowed" <> help "Fails after upgrading if the upgraded ghcup binary is shadowed by something else in PATH (useful for CI)")
                   )
              <**> helper
              )
              (progDesc "Upgrade ghcup")
            )
-#endif
       <> command
            "compile"
            (   Compile
diff --git a/app/ghcup/GHCup/OptParse/Upgrade.hs b/app/ghcup/GHCup/OptParse/Upgrade.hs
index 8578228320caea8954b6877cb6295f6059c130dd..bceb4dcc36313a7abda5d8e2e852f26793773422 100644
--- a/app/ghcup/GHCup/OptParse/Upgrade.hs
+++ b/app/ghcup/GHCup/OptParse/Upgrade.hs
@@ -59,15 +59,16 @@ data UpgradeOpts = UpgradeInplace
     --[ Parsers ]--
     ---------------
 
-          
+
 upgradeOptsP :: Parser UpgradeOpts
 upgradeOptsP =
   flag'
       UpgradeInplace
       (short 'i' <> long "inplace" <> help
-        "Upgrade ghcup in-place (wherever it's at)"
+        "Upgrade ghcup in-place"
       )
-    <|> (   UpgradeAt
+    <|>
+      (   UpgradeAt
         <$> option
               str
               (short 't' <> long "target" <> metavar "TARGET_DIR" <> help
@@ -92,6 +93,7 @@ type UpgradeEffects = '[ DigestError
                        , FileDoesNotExistError
                        , CopyError
                        , DownloadFailed
+                       , GHCupShadowed
                        ]
 
 
@@ -120,18 +122,19 @@ upgrade :: ( Monad m
            )
         => UpgradeOpts
         -> Bool
+        -> Bool
         -> Dirs
         -> (forall a. ReaderT AppState m (VEither UpgradeEffects a) -> m (VEither UpgradeEffects a))
         -> (ReaderT LeanAppState m () -> m ())
         -> m ExitCode
-upgrade uOpts force' Dirs{..} runAppState runLogger = do
+upgrade uOpts force' fatal Dirs{..} runAppState runLogger = do
   target <- case uOpts of
     UpgradeInplace  -> Just <$> liftIO getExecutablePath
     (UpgradeAt p)   -> pure $ Just p
     UpgradeGHCupDir -> pure (Just (binDir </> "ghcup" <> exeExt))
 
   runUpgrade runAppState (do
-    v' <- liftE $ upgradeGHCup target force'
+    v' <- liftE $ upgradeGHCup target force' fatal
     GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
     pure (v', dls)
     ) >>= \case
diff --git a/app/ghcup/Main.hs b/app/ghcup/Main.hs
index c52b066e0fb2b8b4b61dc613b0103a5dd6b043de..2dd0f6ee2231423b8667f433b9cc22333a4aca15 100644
--- a/app/ghcup/Main.hs
+++ b/app/ghcup/Main.hs
@@ -141,9 +141,7 @@ main = do
         )
   let listCommands = infoOption
         ("install set rm install-cabal list"
-#ifndef DISABLE_UPGRADE
           <> " upgrade"
-#endif
           <> " compile debug-info tool-requirements changelog"
         )
         (  long "list-commands"
@@ -245,14 +243,10 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
                              alreadyInstalling' <- alreadyInstalling optCommand newTool
                              when (not alreadyInstalling') $
                                case t of
-#ifdef DISABLE_UPGRADE
-                                 GHCup -> pure ()
-#else
                                  GHCup -> runLogger $
                                             logWarn ("New GHCup version available: "
                                               <> prettyVer l
                                               <> ". To upgrade, run 'ghcup upgrade'")
-#endif
                                  _ -> runLogger $
                                         logWarn ("New "
                                           <> T.pack (prettyShow t)
@@ -296,26 +290,24 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
               s' <- appState
               liftIO $ brickMain s' >> pure ExitSuccess
 #endif
-            Install installCommand   -> install installCommand settings appState runLogger
-            InstallCabalLegacy iopts -> install (Left (InstallCabal iopts)) settings appState runLogger
-            Set setCommand           -> set setCommand runAppState runLeanAppState runLogger
-            UnSet unsetCommand       -> unset unsetCommand runLeanAppState runLogger
-            List lo                  -> list lo no_color runAppState
-            Rm rmCommand             -> rm rmCommand runAppState runLogger
-            DInfo                    -> dinfo runAppState runLogger
-            Compile compileCommand   -> compile compileCommand settings dirs runAppState runLogger
-            Config configCommand     -> config configCommand settings keybindings runLogger
+            Install installCommand     -> install installCommand settings appState runLogger
+            InstallCabalLegacy iopts   -> install (Left (InstallCabal iopts)) settings appState runLogger
+            Set setCommand             -> set setCommand runAppState runLeanAppState runLogger
+            UnSet unsetCommand         -> unset unsetCommand runLeanAppState runLogger
+            List lo                    -> list lo no_color runAppState
+            Rm rmCommand               -> rm rmCommand runAppState runLogger
+            DInfo                      -> dinfo runAppState runLogger
+            Compile compileCommand     -> compile compileCommand settings dirs runAppState runLogger
+            Config configCommand       -> config configCommand settings keybindings runLogger
             Whereis whereisOptions
-                    whereisCommand   -> whereis whereisCommand whereisOptions runAppState leanAppstate runLogger
-#ifndef DISABLE_UPGRADE
-            Upgrade uOpts force'     -> upgrade uOpts force' dirs runAppState runLogger
-#endif
-            ToolRequirements topts   -> toolRequirements topts runAppState runLogger
-            ChangeLog changelogOpts  -> changelog changelogOpts runAppState runLogger
-            Nuke                     -> nuke appState runLogger
-            Prefetch pfCom           -> prefetch pfCom runAppState runLogger
-            GC gcOpts                -> gc gcOpts runAppState runLogger
-            Run runCommand           -> run runCommand appState leanAppstate runLogger
+                    whereisCommand     -> whereis whereisCommand whereisOptions runAppState leanAppstate runLogger
+            Upgrade uOpts force' fatal -> upgrade uOpts force' fatal dirs runAppState runLogger
+            ToolRequirements topts     -> toolRequirements topts runAppState runLogger
+            ChangeLog changelogOpts    -> changelog changelogOpts runAppState runLogger
+            Nuke                       -> nuke appState runLogger
+            Prefetch pfCom             -> prefetch pfCom runAppState runLogger
+            GC gcOpts                  -> gc gcOpts runAppState runLogger
+            Run runCommand             -> run runCommand appState leanAppstate runLogger
 
           case res of
             ExitSuccess        -> pure ()
@@ -353,9 +345,7 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
     (HLS, ver)   = cmp' HLS (Just $ ToolVersion (mkTVer over)) ver
   alreadyInstalling (Compile (CompileHLS HLSCompileOptions{ targetHLS = Left tver }))
     (HLS, ver)   = cmp' HLS (Just $ ToolVersion (mkTVer tver)) ver
-#ifndef DISABLE_UPGRADE
-  alreadyInstalling (Upgrade _ _) (GHCup, _) = pure True
-#endif
+  alreadyInstalling (Upgrade _ _ _) (GHCup, _) = pure True
   alreadyInstalling _ _ = pure False
 
   cmp' :: ( HasLog env
diff --git a/docs/guide.md b/docs/guide.md
index e54b754e8c635d5448fb79ca44e2fd7f774415aa..9bfb94935ff84bef08cdb001c9e2f91c785e7ad5 100644
--- a/docs/guide.md
+++ b/docs/guide.md
@@ -57,6 +57,13 @@ as e.g. `/etc/bash_completion.d/ghcup` (depending on distro)
 and make sure your bashrc sources the startup script
 (`/usr/share/bash-completion/bash_completion` on some distros).
 
+## Portability
+
+`ghcup` is very portable. There are a few exceptions though:
+
+1. `ghcup tui` is only available on non-windows platforms
+2. legacy subcommands `ghcup install` (without a tool identifier) and `ghcup install-cabal` may be removed in the future
+
 # Configuration
 
 A configuration file can be put in `~/.ghcup/config.yaml`. The default config file
diff --git a/ghcup.cabal b/ghcup.cabal
index a0247f5a0c04c24a672435d16d8e7dc921f45ef3..708b53d84bf0d7ca31464456017d6fbd15e09111 100644
--- a/ghcup.cabal
+++ b/ghcup.cabal
@@ -48,13 +48,6 @@ flag no-exe
   default:     False
   manual:      True
 
-flag disable-upgrade
-  description:
-    Disable upgrade functionality. This is mainly to support brew packagers.
-
-  default:     False
-  manual:      True
-
 library
   exposed-modules:
     GHCup
@@ -204,6 +197,7 @@ executable ghcup
     GHCup.OptParse.Set
     GHCup.OptParse.ToolRequirements
     GHCup.OptParse.UnSet
+    GHCup.OptParse.Upgrade
     GHCup.OptParse.Whereis
 
   hs-source-dirs:     app/ghcup
@@ -277,11 +271,6 @@ executable ghcup
   if flag(no-exe)
     buildable: False
 
-  if flag(disable-upgrade)
-    cpp-options: -DDISABLE_UPGRADE
-
-  else
-    other-modules: GHCup.OptParse.Upgrade
 
 test-suite ghcup-test
   type:               exitcode-stdio-1.0
diff --git a/lib/GHCup.hs b/lib/GHCup.hs
index 39f079a097796b927cc9afc51d4fe1bc73c20c00..b358b02865d29180f0b46a459a75bfa18d72fbd8 100644
--- a/lib/GHCup.hs
+++ b/lib/GHCup.hs
@@ -2587,6 +2587,7 @@ upgradeGHCup :: ( MonadMask m
              => Maybe FilePath    -- ^ full file destination to write ghcup into
              -> Bool              -- ^ whether to force update regardless
                                   --   of currently installed version
+             -> Bool              -- ^ whether to throw an error if ghcup is shadowed
              -> Excepts
                   '[ CopyError
                    , DigestError
@@ -2595,10 +2596,11 @@ upgradeGHCup :: ( MonadMask m
                    , DownloadFailed
                    , NoDownload
                    , NoUpdate
+                   , GHCupShadowed
                    ]
                   m
                   Version
-upgradeGHCup mtarget force' = do
+upgradeGHCup mtarget force' fatal = do
   Dirs {..} <- lift getDirs
   GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
 
@@ -2625,15 +2627,18 @@ upgradeGHCup mtarget force' = do
     lift $ logWarn $ T.pack (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 $ "ghcup is shadowed by "
-      <> T.pack pa
-      <> ". The upgrade will not be in effect, unless you remove "
-      <> T.pack pa
-      <> " or make sure "
-      <> T.pack destDir
-      <> " comes before "
-      <> T.pack (takeFileName pa)
-      <> " in PATH."
+    Just pa
+      | fatal -> throwE (GHCupShadowed pa destFile latestVer)
+      | otherwise ->
+        lift $ logWarn $ "ghcup is shadowed by "
+          <> T.pack pa
+          <> ". The upgrade will not be in effect, unless you remove "
+          <> T.pack pa
+          <> " or make sure "
+          <> T.pack destDir
+          <> " comes before "
+          <> T.pack (takeDirectory pa)
+          <> " in PATH."
 
   pure latestVer
 
diff --git a/lib/GHCup/Errors.hs b/lib/GHCup/Errors.hs
index 4d8b18315f7ea752b068e7c7e496ed44f237a1d6..df72bd2499b0b6c748585cd24cab4f2b86c90c89 100644
--- a/lib/GHCup/Errors.hs
+++ b/lib/GHCup/Errors.hs
@@ -27,6 +27,7 @@ import           Data.CaseInsensitive           ( CI )
 import           Data.Text                      ( Text )
 import           Data.Versions
 import           Haskus.Utils.Variant
+import           System.FilePath
 import           Text.PrettyPrint               hiding ( (<>) )
 import           Text.PrettyPrint.HughesPJClass hiding ( (<>) )
 import           URI.ByteString
@@ -291,6 +292,24 @@ instance Pretty HadrianNotFound where
   pPrint HadrianNotFound =
     text "Could not find Hadrian build files. Does this GHC version support Hadrian builds?"
 
+data GHCupShadowed = GHCupShadowed
+                       FilePath  -- shadow binary
+                       FilePath  -- upgraded binary
+                       Version   -- upgraded version
+  deriving Show
+
+instance Pretty GHCupShadowed where
+  pPrint (GHCupShadowed sh up _) =
+    text ("ghcup is shadowed by "
+         <> sh
+         <> ". The upgrade will not be in effect, unless you remove "
+         <> sh
+         <> " or make sure "
+         <> takeDirectory up
+         <> " comes before "
+         <> takeDirectory sh
+         <> " in PATH."
+         )
 
     -------------------------
     --[ High-level errors ]--