diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Changelog
 
+## 0.5 (2025-09-18)
+- support Cabal's XDG layout (#5, reported by Artem)
+  ie check for ~/.config/cabal/config before ~/.cabal/config
+- do not prompt for password if there is is 'token' or 'password-command' (#5)
+- upload-haddock/publish-haddock: now builds docs first with v2-haddock (#6)
+- bump default stack init to lts-23
+
 ## 0.4.8 (2025-05-23)
 - bump stack default to lts-22
 
diff --git a/hkgr.cabal b/hkgr.cabal
--- a/hkgr.cabal
+++ b/hkgr.cabal
@@ -1,6 +1,6 @@
 cabal-version:       2.0
 name:                hkgr
-version:             0.4.8
+version:             0.5
 synopsis:            Simple Hackage release workflow for package maintainers
 description:
             Hkgr (Hackager) is a tool to help make new releases of
@@ -20,9 +20,9 @@
 data-files:          template.cabal.tmpl
 extra-doc-files:     README.md
                    , CHANGELOG.md
-tested-with:         GHC == 9.10 || == 9.8 || == 9.6 || == 9.4 || == 9.2
-                     || == 9.0 ||  == 8.10 || == 8.8 || == 8.6 || == 8.4
-                     || == 8.2
+tested-with:         GHC == 9.12 || == 9.10 || == 9.8 || == 9.6 || == 9.4
+                     || == 9.2 || == 9.0 ||  == 8.10 || == 8.8 || == 8.6
+                     || == 8.4 || == 8.2
 
 source-repository head
   type:                git
@@ -61,4 +61,3 @@
     ghc-options:       -Wunused-packages
 
   default-language:    Haskell2010
-  default-extensions:  OverloadedStrings
diff --git a/src/Main.hs b/src/Main.hs
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -18,7 +18,7 @@
 import SimpleCmdArgs
 import SimplePrompt (promptNonEmpty, promptPassword)
 import System.Directory
-import System.Environment.XDG.BaseDir
+import System.Environment.XDG.BaseDir (getUserConfigFile)
 import System.Exit (ExitCode (..))
 import System.FilePath
 import System.IO (BufferMode(NoBuffering), hSetBuffering, stdout)
@@ -53,11 +53,9 @@
       <$> nobuildOpt
       <*> switchWith 'U' "no-upload" "Do not upload to Hackage"
     , Subcommand "upload-haddock" "Upload candidate documentation to Hackage" $
-      upHaddockCmd False
-      <$> forceOpt "rebuild doc tarball"
+      pure (upHaddockCmd False)
     , Subcommand "publish-haddock" "Publish documentation to Hackage" $
-      upHaddockCmd True
-      <$> forceOpt "rebuild doc tarball"
+      pure (upHaddockCmd True)
     , Subcommand "build" "Do a local pristine build from the tarball" $
       pure pristineBuildCmd
     , Subcommand "version" "Show the package version from .cabal file" $
@@ -92,11 +90,13 @@
   (== ExitSuccess) <$> P.runProcess (git c args)
 
 removeTrailingNewline :: B.ByteString -> B.ByteString
-removeTrailingNewline "" = ""
 removeTrailingNewline bs =
-  if B.last bs == '\n'
-  then B.init bs
-  else bs
+  case B.unsnoc bs of
+    Nothing -> bs
+    Just (ini,l) ->
+      if l == '\n'
+      then ini
+      else bs
 
 cmd :: String -> [String] -> IO String
 cmd c args =
@@ -285,44 +285,52 @@
 getUserPassword = do
   cabalConfig <- do
     home <- getHomeDirectory
-    let cabalConfig = home </> ".cabal/config"
-    exists <- doesFileExist cabalConfig
-    if exists
-      then readFile cabalConfig
+    xdgConfig <- getUserConfigFile "cabal" "config"
+    have_xdg <- doesFileExist xdgConfig
+    if have_xdg
+      then readFile xdgConfig
       else do
-      putStrLn $ "Warning:" +-+ cabalConfig +-+ "not found!"
-      return ""
-  muser <- maybeGetHackage "username" False cabalConfig
-  mpasswd <- maybeGetHackage "password" True cabalConfig
+      let config = home </> ".cabal/config"
+      exists <- doesFileExist config
+      if exists
+        then readFile config
+        else do
+        putStrLn $ "Warning: no cabal" +-+ show "config" +-+ "file found!"
+        return ""
+  muser <- maybeGetUsername cabalConfig
+  mpasswd <- maybeGetPassword cabalConfig
   return $ B.pack $ unlines $ catMaybes [muser, mpasswd]
   where
-    maybeGetHackage :: String -> Bool -> String -> IO (Maybe String)
-    maybeGetHackage field hide cabalConfig =
-      if haveCabalConfigField
+    maybeGetUsername :: String -> IO (Maybe String)
+    maybeGetUsername config =
+      if haveField config "username"
         then return Nothing
-        else Just <$> prompt hide ("hackage.haskell.org" +-+ field)
-      where
-        haveCabalConfigField :: Bool
-        haveCabalConfigField =
-          any ((field ++ ":") `isPrefixOf`) (lines cabalConfig)
+        else Just <$> prompt False "hackage.haskell.org username"
 
+    maybeGetPassword :: String -> IO (Maybe String)
+    maybeGetPassword config =
+      if any (haveField config) ["password", "token", "password-command"]
+        then return Nothing
+        else Just <$> prompt True "hackage.haskell.org password"
+
+    haveField :: String -> String -> Bool
+    haveField config field =
+      any ((field ++ ":") `isPrefixOf`) (lines config)
+
 prompt :: Bool -> String -> IO String
 prompt hide s = do
   inp <- (if hide then promptPassword else promptNonEmpty) s
   when hide $ putChar '\n'
   return inp
 
-upHaddockCmd :: Bool -> Bool -> IO ()
-upHaddockCmd publish force = do
+upHaddockCmd :: Bool -> IO ()
+upHaddockCmd publish = do
   needProgram "cabal"
   userpassBS <- getUserPassword
   pkgid <- getPackageId
-  when force $ do
-    let tarball = "dist" </> showPkgId pkgid ++ '-' : "docs" <.> tarGzExt
-    exists <- doesFileExist tarball
-    when exists $
-      removeFile tarball
-  _out <- P.readProcessInterleaved_ (P.setStdin (P.byteStringInput userpassBS) $ P.proc "cabal" ("upload" : "--documentation" : ["--publish" | publish]))
+  let tarball = "dist-newstyle" </> showPkgId pkgid ++ '-' : "docs" <.> tarGzExt
+  cabal_ "haddock" ["--haddock-for-hackage"]
+  _out <- P.readProcessInterleaved_ (P.setStdin (P.byteStringInput userpassBS) $ P.proc "cabal" ("upload" : "--documentation" : ["--publish" | publish] ++ [tarball]))
   putStrLn $ (if publish then "Published at" else "Uploaded to") +-+ "https://hackage.haskell.org/package/" ++ showPkgId pkgid ++ if publish then "" else "/candidate"
 
 cabal_ :: String -> [String] -> IO ()
@@ -395,7 +403,7 @@
   -- FIXME add stack.yaml template too
   when (not haveStackCfg && isJust mstack) $ do
     -- FIXME determine last lts automatically
-    cmd_ "stack" ["init", "--verbosity", "warn", "--resolver", "lts-22"]
+    cmd_ "stack" ["init", "--verbosity", "warn", "--resolver", "lts-23"]
     sed ["/^#/d", "/^$/d"] "stack.yaml"
   haveGit <- doesDirectoryExist ".git"
   unless haveGit $ do
