diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,19 @@
 
 ## Unreleased
 
+## [0.5.0] - 2020-11-06
+
+### Added
+
+- Allow specifying output directory to write nix.conf and netrc files. 
+- Allow pushing without a Signing key using only auth token
+- Allow setting auth token via `$CACHIX_AUTH_TOKEN` shell variable
+
+### Fixed
+
+- Watch store command now pushes the full closure of each store path
+- Support groups when parsing trusted-users from nix.conf
+
 ## [0.3.8] - 2020-06-03
 
 ### Added
@@ -210,7 +223,8 @@
 
 - Initial release @domenkozar
 
-[Unreleased]: https://github.com/cachix/cachix/compare/v0.3.7...HEAD
+[Unreleased]: https://github.com/cachix/cachix/compare/v0.3.8...HEAD
+[0.3.8]: https://github.com/cachix/cachix/compare/v0.3.7...v0.3.8
 [0.3.7]: https://github.com/cachix/cachix/compare/v0.3.6...v0.3.7
 [0.3.6]: https://github.com/cachix/cachix/compare/v0.3.5...v0.3.6
 [0.3.5]: https://github.com/cachix/cachix/compare/v0.3.4...v0.3.5
diff --git a/cachix.cabal b/cachix.cabal
--- a/cachix.cabal
+++ b/cachix.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.2
 name:               cachix
-version: 0.3.8
+version: 0.5.0
 license:            Apache-2.0
 license-file:       LICENSE
 copyright:          2018 Domen Kožar
@@ -106,11 +106,11 @@
     , resourcet
     , retry
     , safe-exceptions
-    , servant               >=0.15
+    , servant               >=0.16
     , servant-auth
     , servant-auth-client   >=0.3.3.0
-    , servant-client        >=0.15
-    , servant-client-core   >=0.15
+    , servant-client        >=0.16
+    , servant-client-core   >=0.16
     , servant-conduit
     , text
     , unix
diff --git a/src/Cachix/Client/Commands.hs b/src/Cachix/Client/Commands.hs
--- a/src/Cachix/Client/Commands.hs
+++ b/src/Cachix/Client/Commands.hs
@@ -12,8 +12,8 @@
   )
 where
 
-import qualified Cachix.Api as Api
-import Cachix.Api.Error
+import qualified Cachix.API as API
+import Cachix.API.Error
 import Cachix.Client.Config
   ( BinaryCacheConfig (BinaryCacheConfig),
     Config (..),
@@ -36,9 +36,9 @@
 import Cachix.Client.Secrets
   ( SigningKey (SigningKey),
     exportSigningKey,
-    parseSigningKeyLenient,
   )
 import Cachix.Client.Servant
+import Cachix.Client.Store (Store)
 import qualified Cachix.Types.SigningKeyCreate as SigningKeyCreate
 import Control.Exception.Safe (handle, throwM)
 import Control.Retry (RetryStatus (rsIterNumber))
@@ -54,7 +54,6 @@
 import Servant.Client.Streaming
 import Servant.Conduit ()
 import System.Directory (doesFileExist)
-import System.Environment (lookupEnv)
 import System.FSNotify
 import System.IO (hIsTerminalDevice)
 
@@ -62,16 +61,16 @@
 authtoken env token = do
   -- TODO: check that token actually authenticates!
   writeConfig (configPath (cachixoptions env)) $ case config env of
-    Just cfg -> cfg {authToken = Token (toS token)}
+    Just cfg -> Config.setAuthToken cfg $ Token (toS token)
     Nothing -> mkConfig token
 
 create :: Env -> Text -> IO ()
 create _ _ =
-  throwIO $ DeprecatedCommand "Create command has been deprecated. Please visit https://cachix.org to create a binary cache."
+  throwIO $ DeprecatedCommand "Create command has been deprecated. Please visit https://app.cachix.org to create a binary cache."
 
 generateKeypair :: Env -> Text -> IO ()
-generateKeypair Env {config = Nothing} _ = throwIO $ NoConfig "Start with visiting https://cachix.org and copying the token to $ cachix authtoken <token>"
-generateKeypair env@Env {config = Just cfg} name = do
+generateKeypair env name = do
+  cachixAuthToken <- Config.getAuthTokenRequired (config env)
   (PublicKey pk, sk) <- createKeypair
   let signingKey = exportSigningKey $ SigningKey sk
       signingKeyCreate = SigningKeyCreate.SigningKeyCreate (toS $ B64.encode pk)
@@ -79,9 +78,12 @@
   -- we first validate if key can be added to the binary cache
   (_ :: NoContent) <-
     escalate <=< (`runClientM` clientenv env) $
-      Api.createKey (cachixBCClient name) (authToken cfg) signingKeyCreate
+      API.createKey cachixClient cachixAuthToken name signingKeyCreate
   -- if key was successfully added, write it to the config
   -- TODO: warn if binary cache with the same key already exists
+  let cfg = case config env of
+        Just it -> it
+        Nothing -> Config.mkConfig $ toS $ getToken cachixAuthToken
   writeConfig (configPath (cachixoptions env)) $
     cfg {binaryCaches = binaryCaches cfg <> [bcc]}
   putStrLn
@@ -105,26 +107,24 @@
         Text
     )
 
-envToToken :: Env -> Token
-envToToken env =
-  maybe (Token "") authToken (config env)
-
-notAuthenticatedBinaryCache :: CachixException
-notAuthenticatedBinaryCache =
-  AccessDeniedBinaryCache "You must first authenticate using:  $ cachix authtoken <token>"
+notAuthenticatedBinaryCache :: Text -> CachixException
+notAuthenticatedBinaryCache name =
+  AccessDeniedBinaryCache $
+    "Binary cache " <> name <> " doesn't exist or it's private. " <> Config.noAuthTokenError
 
 accessDeniedBinaryCache :: Text -> CachixException
 accessDeniedBinaryCache name =
-  AccessDeniedBinaryCache $ "You don't seem to have API access to binary cache " <> name
+  AccessDeniedBinaryCache $ "Binary cache " <> name <> " doesn't exist or it's private and you don't have access it"
 
 use :: Env -> Text -> InstallationMode.UseOptions -> IO ()
 use env name useOptions = do
+  cachixAuthToken <- Config.getAuthTokenOptional (config env)
   -- 1. get cache public key
-  res <- (`runClientM` clientenv env) $ Api.get (cachixBCClient name) (envToToken env)
+  res <- (`runClientM` clientenv env) $ API.getCache cachixClient cachixAuthToken name
   case res of
     Left err
       | isErr err status401 && isJust (config env) -> throwM $ accessDeniedBinaryCache name
-      | isErr err status401 -> throwM notAuthenticatedBinaryCache
+      | isErr err status401 -> throwM $ notAuthenticatedBinaryCache name
       | isErr err status404 -> throwM $ BinaryCacheNotFound $ "Binary cache" <> name <> " does not exist."
       | otherwise -> throwM err
     Right binaryCache -> do
@@ -140,7 +140,7 @@
                 InstallationMode.isNixOS = isNixOS
               }
       InstallationMode.addBinaryCache (config env) binaryCache useOptions $
-        fromMaybe (InstallationMode.getInstallationMode nixEnv) (InstallationMode.useMode useOptions)
+        InstallationMode.getInstallationMode nixEnv useOptions
 
 -- TODO: lots of room for performance improvements
 push :: Env -> PushArguments -> IO ()
@@ -156,7 +156,7 @@
       -- that may or may not be written to by the caller.
       -- This is somewhat like the behavior of `cat` for example.
       (_, paths) -> return paths
-  sk <- findSigningKey env name
+  cacheSecret <- findPushSecret (config env) name
   store <- wait (storeAsync env)
   void $
     pushClosure
@@ -164,28 +164,37 @@
       (clientenv env)
       store
       PushCache
-        { pushCacheToken = envToToken env,
-          pushCacheName = name,
-          pushCacheSigningKey = sk
+        { pushCacheName = name,
+          pushCacheSecret = cacheSecret
         }
       (pushStrategy env opts name)
       inputStorePaths
   putText "All done."
 push env (PushWatchStore opts name) = withManager $ \mgr -> do
-  _ <- watchDir mgr "/nix/store" filterF action
-  _ <- wait (storeAsync env)
-  putText "Watching /nix/store for new builds ..."
-  forever $ threadDelay 1000000
+  store <- wait (storeAsync env)
+  pushSecret <- findPushSecret (config env) name
+  _ <- watchDir mgr "/nix/store" filterF (action store pushSecret)
+  putText "Watching /nix/store for new paths ..."
+  forever $ threadDelay (1000 * 1000)
   where
+    action :: Store -> PushSecret -> Action
+    action store pushSecret (Removed fp _ _) =
+      let storePath = toS $ dropEnd 5 fp
+       in Control.Exception.Safe.handle (logErr fp) $ void $
+            pushClosure
+              (mapConcurrentlyBounded (numJobs opts))
+              (clientenv env)
+              store
+              ( PushCache
+                  { pushCacheName = name,
+                    pushCacheSecret = pushSecret
+                  }
+              )
+              (pushStrategy env opts name)
+              [storePath]
+    action _ _ _ = return ()
     logErr :: FilePath -> SomeException -> IO ()
     logErr fp e = hPutStrLn stderr $ "Exception occured while pushing " <> fp <> ": " <> show e
-    action :: Action
-    action (Removed fp _ _) =
-      Control.Exception.Safe.handle (logErr fp)
-        $ pushStorePath env opts name
-        $ toS
-        $ dropEnd 5 fp
-    action _ = return ()
     filterF :: ActionPredicate
     filterF (Removed fp _ _)
       | ".lock" `isSuffixOf` fp = True
@@ -193,44 +202,6 @@
     dropEnd :: Int -> [a] -> [a]
     dropEnd index xs = take (length xs - index) xs
 
--- | Find a secret key in the 'Config' or environment variable
-findSigningKey ::
-  Env ->
-  -- | Cache name
-  Text ->
-  -- | Secret key or exception
-  IO SigningKey
-findSigningKey env name = do
-  maybeEnvSK <- lookupEnv "CACHIX_SIGNING_KEY"
-  -- we reverse list of caches to prioritize keys added as last
-  let matches c = filter (\bc -> Config.name bc == name) $ reverse $ binaryCaches c
-      maybeBCSK = case config env of
-        Nothing -> Nothing
-        Just c -> Config.secretKey <$> head (matches c)
-  signingKey <- case maybeBCSK <|> toS <$> maybeEnvSK of
-    Just key -> return key
-    Nothing -> throwIO $ NoSigningKey msg
-  escalateAs FatalError $ parseSigningKeyLenient signingKey
-  where
-    msg :: Text
-    msg =
-      [iTrim|
-Signing key not found. 
-
-It is generated by `cachix generate-keypair <name>` and stored in ~/config/cachix/cachix.dhall
-
-There are a few options why this happened:
-
-a) You haven't generated signing key yet for your cache 
-
-b) You have the key but you're pushing from a different machine. 
-   You can set it via $CACHIX_SIGNING_KEY environment variable.
-
-c) You've lost the signing key. In that case it's best to delete the cache and start again.
-   Note that everyone that configured the binary cache will have to do it again to set the new
-   public key.
-      |]
-
 retryText :: RetryStatus -> Text
 retryText retrystatus =
   if rsIterNumber retrystatus == 0
@@ -244,7 +215,7 @@
       on401 =
         if isJust (config env)
           then throwM $ accessDeniedBinaryCache name
-          else throwM notAuthenticatedBinaryCache,
+          else throwM $ notAuthenticatedBinaryCache name,
       onError = throwM,
       onAttempt = \retrystatus size ->
         -- we append newline instead of putStrLn due to https://github.com/haskell/text/issues/242
@@ -253,20 +224,3 @@
       withXzipCompressor = defaultWithXzipCompressorWithLevel (compressionLevel opts),
       Cachix.Client.Push.omitDeriver = Cachix.Client.OptionsParser.omitDeriver opts
     }
-
-pushStorePath :: Env -> PushOptions -> Text -> Text -> IO ()
-pushStorePath env opts name storePath = do
-  sk <- findSigningKey env name
-  -- use secret key from config or env
-  -- TODO: this shouldn't happen for each store path
-  store <- wait (storeAsync env)
-  pushSingleStorePath
-    (clientenv env)
-    store
-    PushCache
-      { pushCacheToken = envToToken env,
-        pushCacheName = name,
-        pushCacheSigningKey = sk
-      }
-    (pushStrategy env opts name storePath)
-    storePath
diff --git a/src/Cachix/Client/Config.hs b/src/Cachix/Client/Config.hs
--- a/src/Cachix/Client/Config.hs
+++ b/src/Cachix/Client/Config.hs
@@ -1,7 +1,13 @@
 {-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE QuasiQuotes #-}
 
 module Cachix.Client.Config
-  ( Config (..),
+  ( Config (binaryCaches),
+    getAuthTokenRequired,
+    getAuthTokenOptional,
+    getAuthTokenMaybe,
+    setAuthToken,
+    noAuthTokenError,
     BinaryCacheConfig (..),
     readConfig,
     writeConfig,
@@ -12,6 +18,8 @@
 where
 
 import Cachix.Client.Config.Orphans ()
+import Cachix.Client.Exception (CachixException (..))
+import Data.String.Here
 import Dhall hiding (Text)
 import Dhall.Pretty (prettyExpr)
 import Protolude
@@ -22,6 +30,7 @@
     doesFileExist,
     getXdgDirectory,
   )
+import System.Environment (lookupEnv)
 import System.FilePath.Posix (takeDirectory)
 import System.Posix.Files
   ( ownerReadMode,
@@ -77,3 +86,42 @@
 assureFilePermissions :: FilePath -> IO ()
 assureFilePermissions fp =
   setFileMode fp $ unionFileModes ownerReadMode ownerWriteMode
+
+getAuthTokenRequired :: Maybe Config -> IO Token
+getAuthTokenRequired maybeConfig = do
+  authTokenMaybe <- getAuthTokenMaybe maybeConfig
+  case authTokenMaybe of
+    Just authtoken -> return authtoken
+    Nothing -> throwIO $ NoConfig $ toS noAuthTokenError
+
+-- TODO: https://github.com/haskell-servant/servant-auth/issues/173
+getAuthTokenOptional :: Maybe Config -> IO Token
+getAuthTokenOptional maybeConfig = do
+  authTokenMaybe <- getAuthTokenMaybe maybeConfig
+  return $ Protolude.maybe (Token "") identity authTokenMaybe
+
+-- get auth token from env variable or fallback to config
+getAuthTokenMaybe :: Maybe Config -> IO (Maybe Token)
+getAuthTokenMaybe maybeConfig = do
+  maybeAuthToken <- lookupEnv "CACHIX_AUTH_TOKEN"
+  case (maybeAuthToken, maybeConfig) of
+    (Just token, _) -> return $ Just $ Token $ toS token
+    (Nothing, Just cfg) -> return $ Just $ authToken cfg
+    (_, _) -> return Nothing
+
+noAuthTokenError :: Text
+noAuthTokenError =
+  [iTrim|
+Start by visiting https://app.cachix.org and either:
+
+a) Via environment variable: 
+
+$ export CACHIX_AUTH_TOKEN=<token...>
+
+b) Running:
+
+$ cachix authtoken <token...>
+  |]
+
+setAuthToken :: Config -> Token -> Config
+setAuthToken cfg token = cfg {authToken = token}
diff --git a/src/Cachix/Client/InstallationMode.hs b/src/Cachix/Client/InstallationMode.hs
--- a/src/Cachix/Client/InstallationMode.hs
+++ b/src/Cachix/Client/InstallationMode.hs
@@ -13,17 +13,20 @@
   )
 where
 
-import qualified Cachix.Api as Api
 import Cachix.Client.Config (Config)
+import qualified Cachix.Client.Config as Config
 import Cachix.Client.Exception (CachixException (..))
 import qualified Cachix.Client.NetRc as NetRc
 import qualified Cachix.Client.NixConf as NixConf
+import qualified Cachix.Types.BinaryCache as BinaryCache
+import qualified Data.Maybe
 import Data.String.Here
 import qualified Data.Text as T
 import Protolude
 import System.Directory (Permissions, createDirectoryIfMissing, getPermissions, writable)
 import System.Environment (lookupEnv)
 import System.FilePath ((</>), replaceFileName)
+import System.Process (readProcessWithExitCode)
 import Prelude (String)
 
 data NixEnv
@@ -44,7 +47,8 @@
 data UseOptions
   = UseOptions
       { useMode :: Maybe InstallationMode,
-        useNixOSFolder :: FilePath
+        useNixOSFolder :: FilePath,
+        useOutputDirectory :: Maybe FilePath
       }
   deriving (Show)
 
@@ -58,12 +62,15 @@
 toString :: InstallationMode -> String
 toString (Install NixConf.Global) = "root-nixconf"
 toString (Install NixConf.Local) = "user-nixconf"
+toString (Install (NixConf.Custom _)) = "custom-nixconf"
 toString WriteNixOS = "nixos"
 toString UntrustedRequiresSudo = "untrusted-requires-sudo"
 toString UntrustedNixOS = "untrusted-nixos"
 
-getInstallationMode :: NixEnv -> InstallationMode
-getInstallationMode nixenv
+getInstallationMode :: NixEnv -> UseOptions -> InstallationMode
+getInstallationMode nixenv useOptions
+  | (isRoot nixenv || isTrusted nixenv) && isJust (useOutputDirectory useOptions) = Install (NixConf.Custom $ Data.Maybe.fromJust $ useOutputDirectory useOptions)
+  | isJust (useMode useOptions) = Data.Maybe.fromJust $ useMode useOptions
   | isNixOS nixenv && isRoot nixenv = WriteNixOS
   | not (isNixOS nixenv) && isRoot nixenv = Install NixConf.Global
   | isTrusted nixenv = Install NixConf.Local
@@ -71,7 +78,7 @@
   | otherwise = UntrustedRequiresSudo
 
 -- | Add a Binary cache to nix.conf, print nixos config or fail
-addBinaryCache :: Maybe Config -> Api.BinaryCache -> UseOptions -> InstallationMode -> IO ()
+addBinaryCache :: Maybe Config -> BinaryCache.BinaryCache -> UseOptions -> InstallationMode -> IO ()
 addBinaryCache _ _ _ UntrustedNixOS = do
   user <- getUser
   throwIO $
@@ -114,8 +121,11 @@
       NixConf.Local -> do
         lnc <- NixConf.read NixConf.Local
         return ([gnc, lnc], lnc)
+      NixConf.Custom _ -> do
+        lnc <- NixConf.read ncl
+        return ([lnc], lnc)
   let nixconf = fromMaybe (NixConf.NixConf []) output
-  netrcLocMaybe <- forM (guard $ not (Api.isPublic bc)) $ const $ addPrivateBinaryCacheNetRC maybeConfig bc ncl
+  netrcLocMaybe <- forM (guard $ not (BinaryCache.isPublic bc)) $ const $ addPrivateBinaryCacheNetRC maybeConfig bc ncl
   let addNetRCLine :: NixConf.NixConf -> NixConf.NixConf
       addNetRCLine = fromMaybe identity $ do
         netrcLoc <- netrcLocMaybe :: Maybe FilePath
@@ -125,9 +135,9 @@
         pure (NixConf.setNetRC $ toS netrcLoc)
   NixConf.write ncl $ addNetRCLine $ NixConf.add bc (catMaybes input) nixconf
   filename <- NixConf.getFilename ncl
-  putStrLn $ "Configured " <> Api.uri bc <> " binary cache in " <> toS filename
+  putStrLn $ "Configured " <> BinaryCache.uri bc <> " binary cache in " <> toS filename
 
-nixosBinaryCache :: Maybe Config -> Api.BinaryCache -> UseOptions -> IO ()
+nixosBinaryCache :: Maybe Config -> BinaryCache.BinaryCache -> UseOptions -> IO ()
 nixosBinaryCache maybeConfig bc UseOptions {useNixOSFolder = baseDirectory} = do
   _ <- try $ createDirectoryIfMissing True $ toS toplevel :: IO (Either SomeException ())
   eitherPermissions <- try $ getPermissions (toS toplevel) :: IO (Either SomeException Permissions)
@@ -140,7 +150,7 @@
     installFiles = do
       writeFile (toS glueModuleFile) glueModule
       writeFile (toS cacheModuleFile) cacheModule
-      unless (Api.isPublic bc) $ void $ addPrivateBinaryCacheNetRC maybeConfig bc NixConf.Global
+      unless (BinaryCache.isPublic bc) $ void $ addPrivateBinaryCacheNetRC maybeConfig bc NixConf.Global
       putText instructions
     configurationNix :: Text
     configurationNix = toS $ toS baseDirectory </> "configuration.nix"
@@ -151,7 +161,7 @@
     glueModuleFile :: Text
     glueModuleFile = toplevel <> ".nix"
     cacheModuleFile :: Text
-    cacheModuleFile = toplevel <> "/" <> toS (Api.name bc) <> ".nix"
+    cacheModuleFile = toplevel <> "/" <> toS (BinaryCache.name bc) <> ".nix"
     noEtcPermissionInstructions :: Text -> Text
     noEtcPermissionInstructions dir =
       [iTrim|
@@ -163,7 +173,7 @@
     instructions =
       [iTrim|
 Cachix configuration written to ${glueModuleFile}.
-Binary cache ${Api.name bc} configuration written to ${cacheModuleFile}.
+Binary cache ${BinaryCache.name bc} configuration written to ${cacheModuleFile}.
 
 To start using cachix add the following to your ${configurationNix}:
 
@@ -195,21 +205,21 @@
 {
   nix = {
     binaryCaches = [
-      "${Api.uri bc}"
+      "${BinaryCache.uri bc}"
     ];
     binaryCachePublicKeys = [
-      ${T.intercalate " " (map (\s -> "\"" <> s <> "\"") (Api.publicSigningKeys bc))}
+      ${T.intercalate " " (map (\s -> "\"" <> s <> "\"") (BinaryCache.publicSigningKeys bc))}
     ];
   };
 }
     |]
 
 -- TODO: allow overriding netrc location
-addPrivateBinaryCacheNetRC :: Maybe Config -> Api.BinaryCache -> NixConf.NixConfLoc -> IO FilePath
+addPrivateBinaryCacheNetRC :: Maybe Config -> BinaryCache.BinaryCache -> NixConf.NixConfLoc -> IO FilePath
 addPrivateBinaryCacheNetRC maybeConfig bc nixconf = do
   filename <- (`replaceFileName` "netrc") <$> NixConf.getFilename nixconf
   case maybeConfig of
-    Nothing -> panic "Run $ cachix authtoken <token>"
+    Nothing -> throwIO $ NoConfig Config.noAuthTokenError
     Just config -> do
       let netrcfile = fromMaybe filename Nothing -- TODO: get netrc from nixconf
       NetRc.add config [bc] netrcfile
@@ -221,14 +231,19 @@
   user <- getUser
   -- to detect single user installations
   permissions <- getPermissions "/nix/store"
-  let isTrustedU = writable permissions || user `elem` users
-  when (not (null groups) && not isTrustedU) $ do
-    -- TODO: support Nix group syntax
-    putText "Warn: cachix doesn't yet support checking if user is trusted via groups, so it will recommend sudo"
-    putStrLn $ "Warn: groups found " <> T.intercalate "," groups
-  return isTrustedU
+  isInAGroup <- userInAnyGroup user
+  return $ writable permissions || user `elem` users || isInAGroup
   where
-    groups = filter (\u -> T.head u == '@') users
+    groups :: [Text]
+    groups = map T.tail $ filter (\u -> T.head u == '@') users
+    userInAnyGroup :: Text -> IO Bool
+    userInAnyGroup user = do
+      isIn <- for groups $ checkUserInGroup user
+      return $ any identity isIn
+    checkUserInGroup :: Text -> Text -> IO Bool
+    checkUserInGroup user groupName = do
+      (_exitcode, out, _err) <- readProcessWithExitCode "id" ["-Gn", toS user] mempty
+      return $ groupName `T.isInfixOf` toS out
 
 getUser :: IO Text
 getUser = do
diff --git a/src/Cachix/Client/NetRc.hs b/src/Cachix/Client/NetRc.hs
--- a/src/Cachix/Client/NetRc.hs
+++ b/src/Cachix/Client/NetRc.hs
@@ -4,16 +4,17 @@
   )
 where
 
-import qualified Cachix.Api as Api
-import Cachix.Api.Error (escalateAs)
-import Cachix.Client.Config (Config, authToken)
+import Cachix.API.Error (escalateAs)
+import Cachix.Client.Config (Config)
+import qualified Cachix.Client.Config as Config
 import Cachix.Client.Exception (CachixException (NetRcParseError))
+import qualified Cachix.Types.BinaryCache as BinaryCache
 import qualified Data.ByteString as BS
 import Data.List (nubBy)
 import qualified Data.Text as T
 import Network.NetRc
 import Protolude
-import Servant.Auth.Client (getToken)
+import Servant.Auth.Client (Token, getToken)
 import System.Directory (createDirectoryIfMissing, doesFileExist)
 import System.FilePath (takeDirectory)
 
@@ -22,34 +23,35 @@
 --   If file under filename doesn't exist it's created.
 add ::
   Config ->
-  [Api.BinaryCache] ->
+  [BinaryCache.BinaryCache] ->
   FilePath ->
   IO ()
 add config binarycaches filename = do
   doesExist <- doesFileExist filename
+  cachixAuthToken <- Config.getAuthTokenRequired (Just config)
   netrc <-
     if doesExist
       then BS.readFile filename >>= parse
       else return $ NetRc [] []
   createDirectoryIfMissing True (takeDirectory filename)
-  BS.writeFile filename $ netRcToByteString $ uniqueAppend netrc
+  BS.writeFile filename $ netRcToByteString $ uniqueAppend cachixAuthToken netrc
   where
     parse :: ByteString -> IO NetRc
     parse contents = escalateAs (NetRcParseError . show) $ parseNetRc filename contents
     -- O(n^2) but who cares?
-    uniqueAppend :: NetRc -> NetRc
-    uniqueAppend (NetRc hosts macdefs) =
+    uniqueAppend :: Token -> NetRc -> NetRc
+    uniqueAppend cachixAuthToken (NetRc hosts macdefs) =
       let f :: NetRcHost -> NetRcHost -> Bool
           f x y = nrhName x == nrhName y
-       in NetRc (nubBy f (new ++ hosts)) macdefs
-    new :: [NetRcHost]
-    new = map mkHost $ filter (not . Api.isPublic) binarycaches
-    mkHost :: Api.BinaryCache -> NetRcHost
-    mkHost bc =
+       in NetRc (nubBy f (new cachixAuthToken ++ hosts)) macdefs
+    new :: Token -> [NetRcHost]
+    new cachixAuthToken = map (mkHost cachixAuthToken) $ filter (not . BinaryCache.isPublic) binarycaches
+    mkHost :: Token -> BinaryCache.BinaryCache -> NetRcHost
+    mkHost cachixAuthToken bc =
       NetRcHost
-        { nrhName = toS $ stripPrefix "http://" $ stripPrefix "https://" (Api.uri bc),
+        { nrhName = toS $ stripPrefix "http://" $ stripPrefix "https://" (BinaryCache.uri bc),
           nrhLogin = "",
-          nrhPassword = getToken (authToken config),
+          nrhPassword = getToken cachixAuthToken,
           nrhAccount = "",
           nrhMacros = []
         }
diff --git a/src/Cachix/Client/NixConf.hs b/src/Cachix/Client/NixConf.hs
--- a/src/Cachix/Client/NixConf.hs
+++ b/src/Cachix/Client/NixConf.hs
@@ -32,7 +32,7 @@
   )
 where
 
-import qualified Cachix.Api as Api
+import qualified Cachix.Types.BinaryCache as BinaryCache
 import Data.Char (isSpace)
 import Data.List (nub)
 import qualified Data.Text as T
@@ -85,14 +85,14 @@
 isTrustedUsers _ = Nothing
 
 -- | Pure version of addIO
-add :: Api.BinaryCache -> [NixConf] -> NixConf -> NixConf
+add :: BinaryCache.BinaryCache -> [NixConf] -> NixConf -> NixConf
 add bc toRead toWrite =
   writeLines isPublicKey (TrustedPublicKeys $ nub publicKeys) $
     writeLines isSubstituter (Substituters $ nub substituters) toWrite
   where
     -- Note: some defaults are always appended since overriding some setttings in nix.conf overrides defaults otherwise
-    substituters = (defaultPublicURI : readLines toRead isSubstituter) <> [Api.uri bc]
-    publicKeys = (defaultSigningKey : readLines toRead isPublicKey) <> Api.publicSigningKeys bc
+    substituters = (defaultPublicURI : readLines toRead isSubstituter) <> [BinaryCache.uri bc]
+    publicKeys = (defaultSigningKey : readLines toRead isPublicKey) <> BinaryCache.publicSigningKeys bc
 
 defaultPublicURI :: Text
 defaultPublicURI = "https://cache.nixos.org"
@@ -141,7 +141,7 @@
     noNetRc (NetRcFile _) = False
     noNetRc _ = True
 
-data NixConfLoc = Global | Local
+data NixConfLoc = Global | Local | Custom FilePath
   deriving (Show, Eq)
 
 getFilename :: NixConfLoc -> IO FilePath
@@ -150,6 +150,7 @@
     case ncl of
       Global -> return "/etc/nix"
       Local -> getXdgDirectory XdgConfig "nix"
+      Custom filepath -> return filepath
   return $ dir <> "/nix.conf"
 
 -- nix.conf Parser
diff --git a/src/Cachix/Client/OptionsParser.hs b/src/Cachix/Client/OptionsParser.hs
--- a/src/Cachix/Client/OptionsParser.hs
+++ b/src/Cachix/Client/OptionsParser.hs
@@ -85,14 +85,14 @@
 parserCachixCommand :: Parser CachixCommand
 parserCachixCommand =
   subparser $
-    command "authtoken" (infoH authtoken (progDesc "Configure token for authentication to cachix.org"))
+    command "authtoken" (infoH authtoken (progDesc "Configure authentication token for communication to cachix.org API"))
       <> command "create" (infoH create (progDesc "DEPRECATED: Go to https://cachix.org instead"))
       <> command "generate-keypair" (infoH generateKeypair (progDesc "Generate keypair for a binary cache"))
-      <> command "push" (infoH push (progDesc "Upload Nix store paths to the binary cache"))
-      <> command "use" (infoH use (progDesc "Configure nix.conf to enable binary cache during builds"))
+      <> command "push" (infoH push (progDesc "Upload Nix store paths to a binary cache"))
+      <> command "use" (infoH use (progDesc "Configure a binary cache by writing nix.conf and netrc files."))
   where
-    nameArg = strArgument (metavar "NAME")
-    authtoken = AuthToken <$> strArgument (metavar "TOKEN")
+    nameArg = strArgument (metavar "CACHE-NAME")
+    authtoken = AuthToken <$> strArgument (metavar "AUTHTOKEN")
     create = Create <$> nameArg
     generateKeypair = GenerateKeypair <$> nameArg
     validatedLevel l =
@@ -146,9 +146,16 @@
                 <*> strOption
                   ( long "nixos-folder"
                       <> short 'd'
-                      <> help "Base directory for NixOS configuration"
+                      <> help "Base directory for NixOS configuration generation"
                       <> value "/etc/nixos/"
                       <> showDefault
+                  )
+                <*> optional
+                  ( strOption
+                      ( long "output-directory"
+                          <> short 'O'
+                          <> help "Output directory where nix.conf and netrc will be updated."
+                      )
                   )
             )
 
diff --git a/src/Cachix/Client/Push.hs b/src/Cachix/Client/Push.hs
--- a/src/Cachix/Client/Push.hs
+++ b/src/Cachix/Client/Push.hs
@@ -1,15 +1,19 @@
 {-# LANGUAGE DataKinds #-}
+{-# LANGUAGE QuasiQuotes #-}
 {-# LANGUAGE Rank2Types #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeOperators #-}
 
+{- This is a standalone module so it shouldn't depend on any CLI state like Env -}
 module Cachix.Client.Push
   ( -- * Pushing a single path
     pushSingleStorePath,
     PushCache (..),
+    PushSecret (..),
     PushStrategy (..),
     defaultWithXzipCompressor,
     defaultWithXzipCompressorWithLevel,
+    findPushSecret,
 
     -- * Pushing a closure of store paths
     pushClosure,
@@ -17,15 +21,18 @@
   )
 where
 
-import qualified Cachix.Api as Api
-import Cachix.Api.Error
-import Cachix.Api.Signing (fingerprint, passthroughHashSink, passthroughHashSinkB16, passthroughSizeSink)
+import qualified Cachix.API as API
+import Cachix.API.Error
+import Cachix.API.Signing (fingerprint, passthroughHashSink, passthroughHashSinkB16, passthroughSizeSink)
+import qualified Cachix.Client.Config as Config
 import Cachix.Client.Exception (CachixException (..))
 import Cachix.Client.Secrets
 import Cachix.Client.Servant
 import Cachix.Client.Store (Store)
 import qualified Cachix.Client.Store as Store
+import qualified Cachix.Types.ByteStringStreaming
 import qualified Cachix.Types.NarInfoCreate as Api
+import qualified Cachix.Types.NarInfoHash as NarInfoHash
 import Control.Concurrent.Async (mapConcurrently)
 import qualified Control.Concurrent.QSem as QSem
 import Control.Exception.Safe (MonadMask, throwM)
@@ -33,26 +40,32 @@
 import Control.Retry (RetryPolicy, RetryStatus, exponentialBackoff, limitRetries, recoverAll)
 import Crypto.Sign.Ed25519
 import qualified Data.ByteString.Base64 as B64
+import Data.Coerce (coerce)
 import Data.Conduit
 import Data.Conduit.Lzma (compress)
-import Data.Conduit.Process
+import Data.Conduit.Process hiding (env)
 import Data.IORef
 import qualified Data.Set as Set
+import Data.String.Here
 import qualified Data.Text as T
 import Network.HTTP.Types (status401, status404)
 import Protolude
 import Servant.API
 import Servant.Auth ()
 import Servant.Auth.Client
-import Servant.Client.Streaming hiding (ClientError)
+import Servant.Client.Streaming
 import Servant.Conduit ()
+import System.Environment (lookupEnv)
 import qualified System.Nix.Base32
 
+data PushSecret
+  = PushToken Token
+  | PushSigningKey SigningKey
+
 data PushCache
   = PushCache
       { pushCacheName :: Text,
-        pushCacheSigningKey :: SigningKey,
-        pushCacheToken :: Token
+        pushCacheSecret :: PushSecret
       }
 
 data PushStrategy m r
@@ -90,13 +103,13 @@
   let storeHash = fst $ splitStorePath $ toS storePath
       name = pushCacheName cache
   -- Check if narinfo already exists
-  -- TODO: query also cache.nixos.org? server-side?
   res <-
     liftIO $ (`runClientM` clientEnv) $
-      Api.narinfoHead
-        (cachixBCClient name)
-        (pushCacheToken cache)
-        (Api.NarInfoC storeHash)
+      API.narinfoHead
+        cachixClient
+        (getCacheAuthToken cache)
+        name
+        (NarInfoHash.NarInfoHash storeHash)
   case res of
     Right NoContent -> onAlreadyPresent cb -- we're done as store path is already in the cache
     Left err
@@ -104,6 +117,11 @@
       | isErr err status401 -> on401 cb
       | otherwise -> onError cb err
 
+getCacheAuthToken :: PushCache -> Token
+getCacheAuthToken cache = case pushCacheSecret cache of
+  PushToken token -> token
+  PushSigningKey _ -> Token ""
+
 uploadStorePath ::
   (MonadMask m, MonadIO m) =>
   -- | cachix base url, connection manager, see 'Cachix.Client.URI.defaultCachixBaseUrl', 'Servant.Client.mkClientEnv'
@@ -153,7 +171,7 @@
     (_ :: NoContent) <-
       liftIO
         $ (`withClientM` newClientEnv)
-          (Api.createNar (cachixBCStreamingClient name) stream')
+          (API.createNar cachixClient (getCacheAuthToken cache) name (mapOutput coerce stream'))
         $ escalate
           >=> \NoContent -> do
             exitcode <- waitForStreamingProcess cph
@@ -173,7 +191,9 @@
       referencesPathSet <- Store.validPathInfoReferences pathinfo
       references <- sort <$> Store.traversePathSet (pure . toS) referencesPathSet
       let fp = fingerprint storePath narHash narSize references
-          sig = dsign (signingSecretKey $ pushCacheSigningKey cache) fp
+          (sig, authToken) = case pushCacheSecret cache of
+            PushToken token -> (Nothing, token)
+            PushSigningKey signKey -> (Just $ toS $ B64.encode $ unSignature $ dsign (signingSecretKey signKey) fp, Token "")
           nic =
             Api.NarInfoCreate
               { Api.cStoreHash = storeHash,
@@ -187,14 +207,16 @@
                   if deriver == Store.unknownDeriver
                     then deriver
                     else T.drop 11 deriver,
-                Api.cSig = toS $ B64.encode $ unSignature sig
+                Api.cSig = sig
               }
       escalate $ Api.isNarInfoCreateValid nic
       -- Upload narinfo with signature
       escalate <=< (`runClientM` clientEnv) $
-        Api.createNarinfo
-          (cachixBCClient name)
-          (Api.NarInfoC storeHash)
+        API.createNarinfo
+          cachixClient
+          authToken
+          name
+          (NarInfoHash.NarInfoHash storeHash)
           nic
     onDone cb
 
@@ -235,21 +257,55 @@
       closure <- Store.computeFSClosure store Store.defaultClosureParams inputs
       Store.traversePathSet (pure . toSL) closure
   -- Check what store paths are missing
-  -- TODO: query also cache.nixos.org? server-side?
   missingHashesList <-
     retryAll $ \_ ->
       escalate
         =<< liftIO
           ( (`runClientM` clientEnv) $
-              Api.narinfoBulk
-                (cachixBCClient (pushCacheName pushCache))
-                (pushCacheToken pushCache)
+              API.narinfoBulk
+                cachixClient
+                (getCacheAuthToken pushCache)
+                (pushCacheName pushCache)
                 (fst . splitStorePath <$> paths)
           )
   let missingHashes = Set.fromList missingHashesList
       missingPaths = filter (\path -> Set.member (fst (splitStorePath path)) missingHashes) paths
-  -- TODO: make pool size configurable, on beefier machines this could be doubled
   traversal (\path -> retryAll $ \retrystatus -> uploadStorePath clientEnv store pushCache (pushStrategy path) path retrystatus) missingPaths
+
+-- TODO: move to a separate module specific to cli
+
+-- | Find auth token or signing key in the 'Config' or environment variable
+findPushSecret ::
+  Maybe Config.Config ->
+  -- | Cache name
+  Text ->
+  -- | Secret key or exception
+  IO PushSecret
+findPushSecret config name = do
+  maybeEnvSK <- lookupEnv "CACHIX_SIGNING_KEY"
+  -- we reverse list of caches to prioritize keys added as last
+  let matches c = filter (\bc -> Config.name bc == name) $ reverse $ Config.binaryCaches c
+      maybeBCSK = case config of
+        Nothing -> Nothing
+        Just c -> Config.secretKey <$> head (matches c)
+  case maybeBCSK <|> toS <$> maybeEnvSK of
+    Just signingKey -> escalateAs FatalError $ PushSigningKey <$> parseSigningKeyLenient signingKey
+    Nothing -> do
+      authtoken <- Config.getAuthTokenMaybe config
+      case authtoken of
+        Just at -> return $ PushToken at
+        Nothing -> throwIO $ NoSigningKey msg
+  where
+    msg :: Text
+    msg =
+      [iTrim|
+Neither auth token nor signing key are present.
+
+They are looked up via $CACHIX_AUTH_TOKEN and $CACHIX_SIGNING_KEY,
+and if missing also looked up from ~/.config/cachix/cachix.dhall
+
+Read https://mycache.cachix.org for instructions how to push to your binary cache.
+    |]
 
 mapConcurrentlyBounded :: Traversable t => Int -> (a -> IO b) -> t a -> IO (t b)
 mapConcurrentlyBounded bound action items = do
diff --git a/src/Cachix/Client/Servant.hs b/src/Cachix/Client/Servant.hs
--- a/src/Cachix/Client/Servant.hs
+++ b/src/Cachix/Client/Servant.hs
@@ -1,63 +1,32 @@
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE DataKinds #-}
-{-# LANGUAGE TypeOperators #-}
 {-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeOperators #-}
 {-# LANGUAGE UndecidableInstances #-}
-{-# OPTIONS_GHC -O0 #-} -- TODO https://github.com/haskell-servant/servant/issues/986
+{-# OPTIONS_GHC -O0 #-}
 
-module Cachix.Client.Servant
-  ( isErr
-  , cachixClient
-  , cachixBCClient
-  , cachixBCStreamingClient
-  , runAuthenticatedClient
-  , Cachix.Client.Servant.ClientError
-  ) where
+-- TODO https://github.com/haskell-servant/servant/issues/986
 
-import           Protolude
+module Cachix.Client.Servant
+  ( isErr,
+    cachixClient,
+  )
+where
 
-import qualified Cachix.Api as Api
-import           Cachix.Api.Error
-import qualified Cachix.Client.Config as Config
-import qualified Cachix.Client.Env as Env
-import qualified Cachix.Client.Exception as Exception
-import           Network.HTTP.Types (Status)
-import           Servant.API.Generic
-import           Servant.Auth             ()
-import           Servant.Auth.Client      (Token)
+import qualified Cachix.API
+import Network.HTTP.Types (Status)
+import Protolude
+import Servant.API.Generic
+import Servant.Auth.Client ()
 import qualified Servant.Client
-import           Servant.Client.Generic   (AsClientT)
-import           Servant.Client.Streaming hiding (ClientError)
-import           Servant.Conduit          ()
+import Servant.Client.Generic (AsClientT)
+import Servant.Client.Streaming
+import Servant.Conduit ()
 
-type ClientError =
-#if !MIN_VERSION_servant_client(0,16,0)
-  Servant.Client.ServantError
-#else
-  Servant.Client.ClientError
-#endif
 
 isErr :: ClientError -> Status -> Bool
-#if MIN_VERSION_servant_client(0,16,0)
 isErr (Servant.Client.FailureResponse _ resp) status
-#else
-isErr (Servant.Client.FailureResponse resp) status
-#endif
   | Servant.Client.responseStatusCode resp == status = True
 isErr _ _ = False
 
-cachixClient :: Api.CachixAPI (AsClientT ClientM)
-cachixClient = fromServant $ client Api.servantApi
-
-cachixBCClient :: Text -> Api.BinaryCacheAPI (AsClientT ClientM)
-cachixBCClient name = fromServant $ Api.cache cachixClient name
-
-cachixBCStreamingClient :: Text -> Api.BinaryCacheStreamingAPI (AsClientT ClientM)
-cachixBCStreamingClient name = fromServant $ client (Proxy :: Proxy Api.BinaryCachStreamingServantAPI) name
-
-runAuthenticatedClient :: NFData a => Env.Env -> (Token -> ClientM a) -> IO a
-runAuthenticatedClient env m = do
-  config <- escalate $ maybeToEither (Exception.NoConfig
-     "Start with visiting https://cachix.org and copying the token to $ cachix authtoken <token>") (Env.config env)
-  escalate <=< (`runClientM` Env.clientenv env) $
-    m (Config.authToken config)
+cachixClient :: Cachix.API.BinaryCacheAPI (AsClientT ClientM)
+cachixClient = fromServant $ client Cachix.API.api
diff --git a/test/InstallationModeSpec.hs b/test/InstallationModeSpec.hs
--- a/test/InstallationModeSpec.hs
+++ b/test/InstallationModeSpec.hs
@@ -5,6 +5,14 @@
 import Protolude
 import Test.Hspec
 
+defautUseOptions :: UseOptions
+defautUseOptions =
+  UseOptions
+    { useMode = Nothing,
+      useOutputDirectory = Nothing,
+      useNixOSFolder = "/etc/nixos"
+    }
+
 spec :: Spec
 spec = describe "getInstallationMode" $ do
   it "NixOS with root prints configuration" $
@@ -14,7 +22,7 @@
               isRoot = True,
               isNixOS = True
             }
-     in getInstallationMode nixenv `shouldBe` WriteNixOS
+     in getInstallationMode nixenv defautUseOptions `shouldBe` WriteNixOS
   it "NixOS without trust prints steps to follow" $
     let nixenv =
           NixEnv
@@ -22,7 +30,7 @@
               isRoot = False,
               isNixOS = True
             }
-     in getInstallationMode nixenv `shouldBe` UntrustedNixOS
+     in getInstallationMode nixenv defautUseOptions `shouldBe` UntrustedNixOS
   it "NixOS without trust prints steps to follow" $
     let nixenv =
           NixEnv
@@ -30,7 +38,7 @@
               isRoot = False,
               isNixOS = True
             }
-     in getInstallationMode nixenv `shouldBe` UntrustedNixOS
+     in getInstallationMode nixenv defautUseOptions `shouldBe` UntrustedNixOS
   it "NixOS non-root trusted results into local install" $
     let nixenv =
           NixEnv
@@ -38,7 +46,7 @@
               isRoot = False,
               isNixOS = True
             }
-     in getInstallationMode nixenv `shouldBe` Install NixConf.Local
+     in getInstallationMode nixenv defautUseOptions `shouldBe` Install NixConf.Local
   it "non-NixOS root results into global install" $
     let nixenv =
           NixEnv
@@ -46,7 +54,7 @@
               isRoot = True,
               isNixOS = False
             }
-     in getInstallationMode nixenv `shouldBe` Install NixConf.Global
+     in getInstallationMode nixenv defautUseOptions `shouldBe` Install NixConf.Global
   it "non-NixOS with Nix 1.X root results into global install" $
     let nixenv =
           NixEnv
@@ -54,7 +62,7 @@
               isRoot = True,
               isNixOS = False -- any
             }
-     in getInstallationMode nixenv `shouldBe` Install NixConf.Global
+     in getInstallationMode nixenv defautUseOptions `shouldBe` Install NixConf.Global
   it "non-NixOS non-root trusted results into local install" $
     let nixenv =
           NixEnv
@@ -62,7 +70,7 @@
               isRoot = False,
               isNixOS = False
             }
-     in getInstallationMode nixenv `shouldBe` Install NixConf.Local
+     in getInstallationMode nixenv defautUseOptions `shouldBe` Install NixConf.Local
   it "non-NixOS non-root non-trusted results into required sudo" $
     let nixenv =
           NixEnv
@@ -70,4 +78,4 @@
               isRoot = False,
               isNixOS = False
             }
-     in getInstallationMode nixenv `shouldBe` UntrustedRequiresSudo
+     in getInstallationMode nixenv defautUseOptions `shouldBe` UntrustedRequiresSudo
diff --git a/test/NetRcSpec.hs b/test/NetRcSpec.hs
--- a/test/NetRcSpec.hs
+++ b/test/NetRcSpec.hs
@@ -1,8 +1,9 @@
 module NetRcSpec where
 
-import Cachix.Api (BinaryCache (..))
-import Cachix.Client.Config (Config (..))
+import Cachix.Client.Config (Config, mkConfig)
 import qualified Cachix.Client.NetRc as NetRc
+import Cachix.Types.BinaryCache (BinaryCache (..))
+import Cachix.Types.Permission (Permission (..))
 import Protolude
 import System.Directory (copyFile)
 import System.IO.Temp (withSystemTempFile)
@@ -15,7 +16,8 @@
       uri = "https://name.cachix.org",
       publicSigningKeys = ["pub"],
       isPublic = False,
-      githubUsername = "foobar"
+      githubUsername = "foobar",
+      permission = Read
     }
 
 bc2 :: BinaryCache
@@ -25,15 +27,12 @@
       uri = "https://name2.cachix.org",
       publicSigningKeys = ["pub2"],
       isPublic = False,
-      githubUsername = "foobar2"
+      githubUsername = "foobar2",
+      permission = Read
     }
 
 config :: Config
-config =
-  Config
-    { authToken = "token123",
-      binaryCaches = []
-    }
+config = mkConfig "token123"
 
 -- TODO: poor man's golden tests, use https://github.com/stackbuilders/hspec-golden
 test :: [BinaryCache] -> Text -> Expectation
diff --git a/test/NixConfSpec.hs b/test/NixConfSpec.hs
--- a/test/NixConfSpec.hs
+++ b/test/NixConfSpec.hs
@@ -2,8 +2,8 @@
 
 module NixConfSpec where
 
-import Cachix.Api (BinaryCache (..))
 import Cachix.Client.NixConf as NixConf
+import Cachix.Types.BinaryCache (BinaryCache (..))
 import Data.String.Here
 import Protolude
 import Test.Hspec
