diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,82 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
+and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
+
+## Unreleased
+
+## [0.2.0] - 2019-03-04
+
+### Added
+
+- #71 new command `cachix generate-keypair` with
+  clearer instructions how to handle the secrets @domenkozar
+- #24 #168 Private binary cache support @domenkozar @roberth
+
+### Changed
+- #158 #160 #166 #91 Improve NixOS experience by
+  writing out nixos modules @domenkozar
+- #170 Get rid of amazonka dependency @domenkozar
+
+## [0.1.3] - 2018-11-26
+
+### Changed
+
+- #77 retry transient HTTP exceptions @domenkozar
+- #151 prevent mingled  store paths output @domenkozar
+- #141 prevent unncessary warning about /etc/nix missing @domenkozar
+- #142 ditch hpack @domenkozar
+
+## [0.1.2] - 2018-09-27
+
+### Changed
+
+- #132 error handling for readProcess invocations @domenkozar
+- #130 only warn about not supporting groups if user is not trusted @domenkozar
+- #128 Generate https://cache.nixos.org when run as root on NixOS @yegortimoschenko
+- #121 bail out if narSize is 0 @domenkozar
+- #123 support passing --config @domenkozar
+- #123 no more spurious warning messages when using "cachix use" @domenkozar
+- #105 pass https://cache.nixos.org explicitly @domenkozar
+
+## [0.1.1] - 2018-08-03
+
+### Added
+
+- #102 Nix 1.0 support @domenkozar
+
+### Changed
+
+- #105 Always specify cachix.nixos.org on NixOS @domenkozar
+- #98, #95 Use LTS-12.X and Servant 0.14.1 @domenkozar
+
+## [0.1.0.2] - 2018-07-06
+
+### Changed
+
+- #95 Upgrade to servant-0.14.1 @domenkozar
+
+## [0.1.0.1] - 2018-07-05
+
+### Changed
+
+- #92 Add build-tool-depends where needed @domenkozar
+- #90 HLint fixes @domenkozar
+- #86 Improve Cabal description and synopsis @domenkozar
+- #87 Support fsnotify 0.3.x @domenkozar
+
+## [0.1.0.0] - 2018-07-01
+
+### Added
+
+- Initial release @domenkozar
+
+[Unreleased]: https://github.com/cachix/cachix/compare/v0.1.4...HEAD
+[0.2.0]: https://github.com/cachix/cachix/compare/v0.1.3...v0.2.0
+[0.1.3]: https://github.com/cachix/cachix/compare/v0.1.2...v0.1.3
+[0.1.2]: https://github.com/cachix/cachix/compare/v0.1.1...v0.1.2
+[0.1.1]: https://github.com/cachix/cachix/compare/v0.1.0.2...v0.1.1
+[0.1.0.2]: https://github.com/cachix/cachix/compare/v0.1.0.1...v0.1.0.2
+[0.1.0.1]: https://github.com/cachix/cachix/compare/v0.1.0.0...v0.1.0.1
diff --git a/ChangeLog.md b/ChangeLog.md
deleted file mode 100644
--- a/ChangeLog.md
+++ /dev/null
@@ -1,61 +0,0 @@
-# Changelog
-
-All notable changes to this project will be documented in this file.
-
-The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
-and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
-
-## Unreleased
-
-## [0.1.3] - 2018-11-26
-
-### Changed
-
-- #77 retry transient HTTP exceptions @domenkozar
-- #151 prevent mingled  store paths output @domenkozar
-- #141 prevent unncessary warning about /etc/nix missing @domenkozar
-- #142 ditch hpack @domenkozar
-
-## [0.1.2] - 2018-09-27
-
-### Changed
-
-- #132 error handling for readProcess invocations @domenkozar
-- #130 only warn about not supporting groups if user is not trusted @domenkozar
-- #128 Generate https://cache.nixos.org when run as root on NixOS @yegortimoschenko
-- #121 bail out if narSize is 0 @domenkozar
-- #123 support passing --config @domenkozar
-- #123 no more spurious warning messages when using "cachix use" @domenkozar
-- #105 pass https://cache.nixos.org explicitly @domenkozar
-
-## [0.1.1] - 2018-08-03
-
-### Added
-
-- #102 Nix 1.0 support @domenkozar
-
-### Changed
-
-- #105 Always specify cachix.nixos.org on NixOS @domenkozar
-- #98, #95 Use LTS-12.X and Servant 0.14.1 @domenkozar
-
-## [0.1.0.2] - 2018-07-06
-
-### Changed
-
-- #95 Upgrade to servant-0.14.1 @domenkozar
-
-## [0.1.0.1] - 2018-07-05
-
-### Changed
-
-- #92 Add build-tool-depends where needed @domenkozar
-- #90 HLint fixes @domenkozar
-- #86 Improve Cabal description and synopsis @domenkozar
-- #87 Support fsnotify 0.3.x @domenkozar
-
-## [0.1.0.0] - 2018-07-01
-
-### Added
-
-- Initial release @domenkozar
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.1.3
+version:        0.2.0
 synopsis:       Command line client for Nix binary cache hosting https://cachix.org
 homepage:       https://github.com/cachix/cachix#readme
 bug-reports:    https://github.com/cachix/cachix/issues
@@ -10,9 +10,10 @@
 license:        Apache-2.0
 license-file:   LICENSE
 build-type:     Simple
-
+data-files: *.input, *.output
+data-dir: test/data
 extra-source-files:
-    ChangeLog.md
+    CHANGELOG.md
     README.md
 
 common defaults
@@ -31,7 +32,9 @@
       Cachix.Client.Commands
       Cachix.Client.Config
       Cachix.Client.Env
+      Cachix.Client.Exception
       Cachix.Client.InstallationMode
+      Cachix.Client.NetRc
       Cachix.Client.NixConf
       Cachix.Client.NixVersion
       Cachix.Client.OptionsParser
@@ -48,7 +51,6 @@
     , base >=4.7 && <5
     , base16-bytestring
     , base64-bytestring
-    , bifunctors
     , bytestring
     , cachix-api
     , conduit >=1.3.0
@@ -67,22 +69,21 @@
     , http-conduit
     , http-types
     , lzma-conduit
-    , megaparsec
+    , megaparsec >= 7.0.0
     , memory
     , mmorph
+    , netrc
     , optparse-applicative
     , process
     , protolude
-    , resourcet
     , retry
     , safe-exceptions
-    , servant >=0.14.1
+    , servant >=0.15
     , servant-auth
     , servant-auth-client >=0.3.3.0
-    , servant-client >=0.14
-    , servant-client-core >=0.14.1
-    , servant-streaming-client >=0.3.0.0
-    , streaming
+    , servant-client >=0.15
+    , servant-client-core >=0.15
+    , servant-conduit
     , text
     , unix
     , uri-bytestring
@@ -110,6 +111,7 @@
   main-is: Main.hs
   other-modules:
       InstallationModeSpec
+      NetRcSpec
       NixConfSpec
       NixVersionSpec
       Spec
@@ -121,6 +123,8 @@
       base >=4.7 && <5
     , cachix
     , cachix-api
+    , directory
     , hspec
     , protolude
     , here
+    , temporary
diff --git a/src/Cachix/Client.hs b/src/Cachix/Client.hs
--- a/src/Cachix/Client.hs
+++ b/src/Cachix/Client.hs
@@ -16,6 +16,7 @@
   case command of
     AuthToken token -> Commands.authtoken env token
     Create name -> Commands.create env name
+    GenerateKeypair name -> Commands.generateKeypair env name
     Push name paths watchStore -> Commands.push env name paths watchStore
-    Use name shouldEchoNixOS -> Commands.use env name shouldEchoNixOS
+    Use name useOptions -> Commands.use env name useOptions
     Version -> putText cachixVersion
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
@@ -5,6 +5,7 @@
 module Cachix.Client.Commands
   ( authtoken
   , create
+  , generateKeypair
   , push
   , use
   ) where
@@ -13,40 +14,40 @@
 import           Control.Concurrent           (threadDelay)
 import qualified Control.Concurrent.QSem       as QSem
 import           Control.Concurrent.Async     (mapConcurrently)
-import           Control.Monad                (forever)
+import           Control.Monad                (forever, (>=>))
 import           Control.Exception.Safe       (MonadThrow, throwM)
-import           Control.Monad.Trans.Resource (ResourceT)
 import           Control.Retry                (recoverAll, RetryStatus(rsIterNumber))
 import qualified Data.ByteString.Base64        as B64
 import           Data.Conduit
 import           Data.Default                  (def)
 import           Data.Conduit.Process
-import qualified Data.Conduit.List             as CL
 import           Data.Conduit.Lzma              ( compress )
 import           Data.IORef
 import           Data.List                      ( isSuffixOf )
 import           Data.Maybe                     ( fromJust )
 import           Data.String.Here
 import qualified Data.Text                      as T
-import           Network.HTTP.Types (status404)
+import           Network.HTTP.Types             (status404, status401)
 import           Protolude
 import           Servant.API
-import           Servant.Client
 import           Servant.Auth                   ()
 import           Servant.Auth.Client
-import           Servant.Streaming.Client       ()
 import           Servant.API.Generic
 import           Servant.Client.Generic
+import           Servant.Client.Streaming
+import           Servant.Conduit                ()
 import           System.Directory               ( doesFileExist )
 import           System.FSNotify
 import           System.IO                      ( stdin, hIsTerminalDevice )
 import           System.Process                 ( readProcess )
 import           System.Environment             ( lookupEnv )
-import qualified Streaming.Prelude             as S
 
 import qualified Cachix.Api                    as Api
+import           Cachix.Api.Error
 import           Cachix.Api.Signing             (fingerprint, passthroughSizeSink, passthroughHashSink)
 import qualified Cachix.Types.NarInfoCreate    as Api
+import qualified Cachix.Types.BinaryCacheCreate as Api
+import qualified Cachix.Types.SigningKeyCreate as SigningKeyCreate
 import           Cachix.Client.Config           ( Config(..)
                                                 , BinaryCacheConfig(..)
                                                 , writeConfig
@@ -54,7 +55,8 @@
                                                 )
 import qualified Cachix.Client.Config          as Config
 import           Cachix.Client.Env              ( Env(..) )
-import           Cachix.Client.OptionsParser    ( CachixOptions(..) )
+import           Cachix.Client.Exception        ( CachixException(..) )
+import           Cachix.Client.OptionsParser    ( CachixOptions(..), UseOptions(..) )
 import           Cachix.Client.InstallationMode
 import           Cachix.Client.NixVersion       ( getNixVersion )
 import qualified Cachix.Client.NixConf         as NixConf
@@ -67,40 +69,42 @@
 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
+
 authtoken :: Env -> Text -> IO ()
 authtoken env token = do
   -- TODO: check that token actually authenticates!
   writeConfig (configPath (cachixoptions env)) $ case config env of
-    Just config -> config { authToken = token }
+    Just config -> config { authToken = Token (toS token) }
     Nothing -> mkConfig token
   putStrLn ([hereLit|
-Continue by creating a binary cache with:
-
-    $ cachix create <name>
-
-and share it with others over https://<name>.cachix.org
+Continue by creating a binary cache at https://cachix.org
   |] :: Text)
 
 create :: Env -> Text -> IO ()
-create Env { config = Nothing } _ = throwIO $ NoConfig "start with: $ cachix authtoken <token>"
-create env@Env { config = Just config } name = do
+create _ _ =
+  throwIO $ DeprecatedCommand "Create command has been deprecated. Please visit https://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 config } name = do
   (PublicKey pk, SecretKey sk) <- createKeypair
 
-  let bc = Api.BinaryCacheCreate $ toS $ B64.encode pk
-  res <- (`runClientM` clientenv env) $ Api.create (cachixBCClient name) (Token (toS (authToken config))) bc
-  case res of
-    -- TODO: handle all kinds of errors
-    Left err -> panic $ show err
-    Right _ -> do
-      -- write signing key to config
-      let signingKey = toS $ B64.encode sk
-          bcc = BinaryCacheConfig name signingKey
+  let signingKey = toS $ B64.encode sk
+      signingKeyCreate = SigningKeyCreate.SigningKeyCreate (toS $ B64.encode pk)
+      bcc = BinaryCacheConfig name signingKey
 
-      writeConfig (configPath (cachixoptions env)) $
-        config { binaryCaches = binaryCaches config <> [bcc] }
+  -- we first validate if key can be added to the binary cache
+  escalate =<< ((`runClientM` clientenv env) $ Api.createKey (cachixBCClient name) (authToken config) signingKeyCreate)
 
-      putStrLn ([iTrim|
-Signing key has been saved on your local machine. To populate
+  -- if key was successfully added, write it to the config
+  -- TODO: this breaks if more than one key is added, see #27
+  writeConfig (configPath (cachixoptions env)) $
+    config { binaryCaches = binaryCaches config <> [bcc] }
+
+  putStrLn ([iTrim|
+Secret signing key has been saved in the file above. To populate
 your binary cache:
 
     $ nix-build | cachix push ${name}
@@ -114,34 +118,46 @@
 
     $ cachix use ${name}
 
-      |] :: Text)
+IMPORTANT: Make sure to make a backup for the signing key above, as you have the only copy.
+  |] :: Text)
 
-use :: Env -> Text -> Bool -> IO ()
-use env name shouldEchoNixOS = do
+envToToken :: Env -> Token
+envToToken env =
+  maybe (Token "") authToken (config env)
+
+notAuthenticatedBinaryCache :: CachixException
+notAuthenticatedBinaryCache =
+  AccessDeniedBinaryCache "You must first authenticate using:  $ cachix authtoken <token>"
+
+accessDeniedBinaryCache :: Text -> CachixException
+accessDeniedBinaryCache name =
+  AccessDeniedBinaryCache $ "You don't seem to have API access to binary cache " <> name
+
+use :: Env -> Text -> UseOptions -> IO ()
+use env name useOptions = do
   -- 1. get cache public key
-  res <- (`runClientM` clientenv env) $ Api.get (cachixBCClient name)
+  res <- (`runClientM` clientenv env) $ Api.get (cachixBCClient name) (envToToken env)
   case res of
-    -- TODO: handle 404
-    Left err -> panic $ show err
+    Left err | isErr err status401 && isJust (config env) -> throwM $ accessDeniedBinaryCache name
+             | isErr err status401 -> throwM notAuthenticatedBinaryCache
+             | isErr err status404 -> throwM $ BinaryCacheNotFound $ "Binary cache" <> name <> " does not exist."
+             | otherwise -> throwM err
     Right binaryCache -> do
-      eitherNixVersion <- getNixVersion
-      case eitherNixVersion of
-        Left err -> panic err
-        Right nixVersion -> do
-          user <- getUser
-          nc <- NixConf.read NixConf.Global
-          isTrusted <- isTrustedUser $ NixConf.readLines (catMaybes [nc]) NixConf.isTrustedUsers
-          isNixOS <- doesFileExist "/etc/NIXOS"
-          let nixEnv = NixEnv
-                { nixVersion = nixVersion
-                , isRoot = user == "root"
-                , isTrusted = isTrusted
-                , isNixOS = isNixOS
-                }
-          addBinaryCache binaryCache $
-            if shouldEchoNixOS
-            then EchoNixOS nixVersion
-            else getInstallationMode nixEnv
+      nixVersion <- escalateAs UnsupportedNixVersion =<< getNixVersion
+      user <- getUser
+      nc <- NixConf.read NixConf.Global
+      isTrusted <- isTrustedUser $ NixConf.readLines (catMaybes [nc]) NixConf.isTrustedUsers
+      isNixOS <- doesFileExist "/etc/NIXOS"
+      let nixEnv = NixEnv
+            { nixVersion = nixVersion
+            , isRoot = user == "root"
+            , isTrusted = isTrusted
+            , isNixOS = isNixOS
+            }
+      addBinaryCache (config env) binaryCache useOptions $
+        if useNixOS useOptions
+        then EchoNixOS nixVersion
+        else getInstallationMode nixEnv
 
 
 -- TODO: lots of room for perfomance improvements
@@ -206,11 +222,14 @@
   -- TODO: query also cache.nixos.org? server-side?
   res <- (`runClientM` clientenv env) $ Api.narinfoHead
     (cachixBCClient name)
+    (envToToken env)
     (Api.NarInfoC storeHash)
   case res of
     Right NoContent -> pass -- we're done as store path is already in the cache
     Left err | isErr err status404 -> go sk retrystatus
-             | otherwise -> escalate $ void res
+             | isErr err status401 && isJust (config env) -> throwM $ accessDeniedBinaryCache name
+             | isErr err status401 -> throwM notAuthenticatedBinaryCache
+             | otherwise -> throwM err
     where
       retryText :: RetryStatus -> Text
       retryText retrystatus =
@@ -238,58 +257,55 @@
              .| passthroughSizeSink fileSizeRef
              .| passthroughHashSink fileHashRef
 
-            conduitToStreaming :: S.Stream (S.Of ByteString) (ResourceT IO) ()
-            conduitToStreaming = runConduit (transPipe lift stream' .| CL.mapM_ S.yield)
         -- for now we need to use letsencrypt domain instead of cloudflare due to its upload limits
         let newClientEnv = (clientenv env) {
               baseUrl = (baseUrl (clientenv env)) { baseUrlHost = toS name <> "." <> baseUrlHost (baseUrl (clientenv env))}
             }
-        NoContent <- escalate <=< (`runClientM` newClientEnv) $ Api.createNar
-          (cachixBCClient name)
-          (contentType (Proxy :: Proxy Api.XNixNar), conduitToStreaming)
-        closeStdout
-        exitcode <- waitForStreamingProcess cph
-        -- TODO: print process stderr?
-        when (exitcode /= ExitSuccess) $ throwM $ NarStreamingError exitcode $ show cmd
+        void $ (`withClientM` newClientEnv)
+            (Api.createNar (cachixBCStreamingClient name) stream')
+            $ escalate >=> \NoContent -> do
+                closeStdout
+                exitcode <- waitForStreamingProcess cph
+                -- TODO: print process stderr?
+                when (exitcode /= ExitSuccess) $ throwM $ NarStreamingError exitcode $ show cmd
 
-        narSize <- readIORef narSizeRef
-        narHashB16 <- readIORef narHashRef
-        fileHash <- readIORef fileHashRef
-        fileSize <- readIORef fileSizeRef
+                narSize <- readIORef narSizeRef
+                narHashB16 <- readIORef narHashRef
+                fileHash <- readIORef fileHashRef
+                fileSize <- readIORef fileSizeRef
 
-        -- TODO: #3: implement using pure haskell
-        narHash <- ("sha256:" <>) . T.strip . toS <$> readProcess "nix-hash" ["--type", "sha256", "--to-base32", toS narHashB16] mempty
+                -- TODO: #3: implement using pure haskell
+                narHash <- ("sha256:" <>) . T.strip . toS <$> readProcess "nix-hash" ["--type", "sha256", "--to-base32", toS narHashB16] mempty
 
-        deriverRaw <- T.strip . toS <$> readProcess "nix-store" ["-q", "--deriver", toS storePath] mempty
-        let deriver = if deriverRaw == "unknown-deriver"
-                      then deriverRaw
-                      else T.drop 11 deriverRaw
+                deriverRaw <- T.strip . toS <$> readProcess "nix-store" ["-q", "--deriver", toS storePath] mempty
+                let deriver = if deriverRaw == "unknown-deriver"
+                              then deriverRaw
+                              else T.drop 11 deriverRaw
 
-        references <- sort . T.lines . T.strip . toS <$> readProcess "nix-store" ["-q", "--references", toS storePath] mempty
+                references <- sort . T.lines . T.strip . toS <$> readProcess "nix-store" ["-q", "--references", toS storePath] mempty
 
-        let
-            fp = fingerprint storePath narHash narSize references
-            sig = dsign sk fp
-            nic = Api.NarInfoCreate
-              { cStoreHash = storeHash
-              , cStoreSuffix = storeSuffix
-              , cNarHash = narHash
-              , cNarSize = narSize
-              , cFileSize = fileSize
-              , cFileHash = fileHash
-              , cReferences = fmap (T.drop 11) references
-              , cDeriver = deriver
-              , cSig = toS $ B64.encode $ unSignature sig
-              }
+                let
+                    fp = fingerprint storePath narHash narSize references
+                    sig = dsign sk fp
+                    nic = Api.NarInfoCreate
+                      { cStoreHash = storeHash
+                      , cStoreSuffix = storeSuffix
+                      , cNarHash = narHash
+                      , cNarSize = narSize
+                      , cFileSize = fileSize
+                      , cFileHash = fileHash
+                      , cReferences = fmap (T.drop 11) references
+                      , cDeriver = deriver
+                      , cSig = toS $ B64.encode $ unSignature sig
+                      }
 
-        escalate $ Api.isNarInfoCreateValid nic
+                escalate $ Api.isNarInfoCreateValid nic
 
-        -- Upload narinfo with signature
-        NoContent <- escalate <=< (`runClientM` clientenv env) $ Api.createNarinfo
-          (cachixBCClient name)
-          (Api.NarInfoC storeHash)
-          nic
-        return ()
+                -- Upload narinfo with signature
+                escalate <=< (`runClientM` clientenv env) $ Api.createNarinfo
+                  (cachixBCClient name)
+                  (Api.NarInfoC storeHash)
+                  nic
 
 
 -- Utils
@@ -298,12 +314,6 @@
 -- Catches all exceptions except skipAsyncExceptions
 retryPath :: (RetryStatus -> IO a) -> IO a
 retryPath = recoverAll def
-
-escalate :: (Exception exc, MonadThrow m) => Either exc a -> m a
-escalate = escalateAs identity
-
-escalateAs :: (Exception exc, MonadThrow m) => (l -> exc) -> Either l a -> m a
-escalateAs f = either (throwM . f) pure
 
 mapConcurrentlyBounded :: Traversable t => Int -> (a -> IO b) -> t a -> IO (t b)
 mapConcurrentlyBounded bound action items = do
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
@@ -11,7 +11,9 @@
 
 import Dhall     hiding ( Text )
 import Dhall.Pretty     ( prettyExpr )
+import Dhall.Core (Expr(..), Chunks(..))
 import GHC.Generics     ( Generic )
+import Servant.Auth.Client
 import System.Directory ( doesFileExist, createDirectoryIfMissing
                         , getXdgDirectory, XdgDirectory(..)
                         )
@@ -27,13 +29,25 @@
  } deriving (Show, Generic, Interpret, Inject)
 
 data Config = Config
- { authToken :: Text
+ { authToken :: Token
  , binaryCaches :: [BinaryCacheConfig]
  } deriving (Show, Generic, Interpret, Inject)
 
+instance Interpret Token where
+  autoWith _ = Type
+    { extract = \(TextLit (Chunks [] t)) -> pure (Token (toS t))
+    , expected = Text
+    }
+
+instance Inject Token where
+  injectWith _ = InputType
+    { embed = TextLit . Chunks [] . toS . getToken
+    , declared = Text
+    }
+
 mkConfig :: Text -> Config
-mkConfig authtoken = Config
-  { authToken = authtoken
+mkConfig token = Config
+  { authToken = Token (toS token)
   , binaryCaches = []
   }
 
diff --git a/src/Cachix/Client/Exception.hs b/src/Cachix/Client/Exception.hs
new file mode 100644
--- /dev/null
+++ b/src/Cachix/Client/Exception.hs
@@ -0,0 +1,20 @@
+module Cachix.Client.Exception (CachixException(..)) where
+
+import Protolude
+
+data CachixException
+  = UnsupportedNixVersion Text
+  | UserEnvNotSet Text
+  | MustBeRoot Text
+  | NixOSInstructions Text
+  | AmbiguousInput Text
+  | NoInput Text
+  | NoConfig Text
+  | NetRcParseError Text
+  | NarStreamingError ExitCode Text
+  | DeprecatedCommand Text
+  | AccessDeniedBinaryCache Text
+  | BinaryCacheNotFound Text
+  deriving (Show, Typeable)
+
+instance Exception CachixException
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
@@ -2,7 +2,6 @@
 module Cachix.Client.InstallationMode
   ( InstallationMode(..)
   , NixEnv(..)
-  , CachixException(..)
   , getInstallationMode
   , addBinaryCache
   , isTrustedUser
@@ -12,24 +11,16 @@
 import           Protolude
 import           Data.String.Here
 import qualified Data.Text as T
+import           Cachix.Client.Config (Config)
+import           Cachix.Client.Exception (CachixException(..))
+import qualified Cachix.Client.NetRc as NetRc
 import qualified Cachix.Client.NixConf as NixConf
 import           Cachix.Client.NixVersion ( NixVersion(..) )
+import           Cachix.Client.OptionsParser    ( UseOptions(..) )
 import           Cachix.Api as Api
-import           System.Directory               ( getPermissions, writable )
+import           System.Directory               ( getPermissions, writable, createDirectoryIfMissing )
 import           System.Environment             ( lookupEnv )
-
-data CachixException
-  = UnsupportedNixVersion Text
-  | UserEnvNotSet Text
-  | MustBeRoot Text
-  | NixOSInstructions Text
-  | AmbiguousInput Text
-  | NoInput Text
-  | NoConfig Text
-  | NarStreamingError ExitCode Text
-  deriving (Show, Typeable)
-
-instance Exception CachixException
+import           System.FilePath                ( replaceFileName, (</>) )
 
 data NixEnv = NixEnv
   { nixVersion :: NixVersion
@@ -57,49 +48,116 @@
 
 
 -- | Add a Binary cache to nix.conf, print nixos config or fail
-addBinaryCache :: Api.BinaryCache -> InstallationMode -> IO ()
-addBinaryCache Api.BinaryCache{..} (EchoNixOS _) = do
-  putText [iTrim|
-nix = {
-  binaryCaches = [
-    "https://cache.nixos.org/"
-    "${uri}"
-  ];
-  binaryCachePublicKeys = [
-    ${T.intercalate " " (map (\s -> "\"" <> s <> "\"") publicSigningKeys)}
-  ];
-};
-  |]
-  throwIO $ NixOSInstructions "Add above lines to your NixOS configuration file"
-addBinaryCache Api.BinaryCache{..} (EchoNixOSWithTrustedUser _) = do
-  -- TODO: DRY
-  user <- getUser
-  putText [iTrim|
-nix = {
-  binaryCaches = [
-    "https://cache.nixos.org/"
-    "${uri}"
-  ];
-  binaryCachePublicKeys = [
-    ${T.intercalate " " (map (\s -> "\"" <> s <> "\"") publicSigningKeys)}
-  ];
-  trustedUsers = [ "root" "${user}" ];
-};
-  |]
-  throwIO $ NixOSInstructions "Add above lines to your NixOS configuration file"
-addBinaryCache _ UntrustedRequiresSudo = throwIO $
+addBinaryCache :: Maybe Config -> Api.BinaryCache -> UseOptions -> InstallationMode -> IO ()
+addBinaryCache _ _ _ UntrustedRequiresSudo = throwIO $
   MustBeRoot "Run command as root OR execute: $ echo \"trusted-users = root $USER\" | sudo tee -a /etc/nix/nix.conf && sudo pkill nix-daemon"
-addBinaryCache _ Nix20RequiresSudo = throwIO $
+addBinaryCache _ _ _ Nix20RequiresSudo = throwIO $
   MustBeRoot "Run command as root OR upgrade to latest Nix - to be able to use it without root (recommended)"
-addBinaryCache bc@Api.BinaryCache{..} (Install nixversion ncl) = do
+addBinaryCache maybeConfig bc useOptions (EchoNixOS _) =
+  nixosBinaryCache maybeConfig Nothing bc useOptions
+addBinaryCache maybeConfig bc useOptions (EchoNixOSWithTrustedUser _) = do
+  user <- getUser
+  nixosBinaryCache maybeConfig (Just user) bc useOptions
+addBinaryCache maybeConfig bc@Api.BinaryCache{..} _ (Install nixversion ncl) = do
   -- TODO: might need locking one day
   gnc <- NixConf.read NixConf.Global
-  lnc <- NixConf.read NixConf.Local
-  let final = if ncl == NixConf.Global then gnc else lnc
-      input = if ncl == NixConf.Global then [gnc] else [gnc, lnc]
-  NixConf.write nixversion ncl $ NixConf.add bc (catMaybes input) (fromMaybe (NixConf.NixConf []) final)
+  (input, output) <- case ncl of
+    NixConf.Global -> return ([gnc], gnc)
+    NixConf.Local -> do
+      lnc <- NixConf.read NixConf.Local
+      return ([gnc, lnc], lnc)
+  let nixconf = fromMaybe (NixConf.NixConf []) output
+  netrcLocMaybe <- forM (guard $ not  isPublic) $ const $ addPrivateBinaryCacheNetRC maybeConfig bc ncl
+  let
+    addNetRCLine :: NixConf.NixConf -> NixConf.NixConf
+    addNetRCLine = fromMaybe identity $ do
+        netrcLoc <- netrcLocMaybe :: Maybe FilePath
+        -- We only add the netrc line for local user configs for now.
+        -- On NixOS we assume it will be picked up from the default location.
+        guard (ncl == NixConf.Local)
+        pure (NixConf.setNetRC $ toS netrcLoc)
+  NixConf.write nixversion ncl $ addNetRCLine $ NixConf.add bc (catMaybes input) nixconf
   filename <- NixConf.getFilename ncl
   putStrLn $ "Configured " <> uri <> " binary cache in " <> toS filename
+
+nixosBinaryCache :: Maybe Config -> Maybe Text -> Api.BinaryCache -> UseOptions -> IO ()
+nixosBinaryCache maybeConfig maybeUser bc@Api.BinaryCache{..} UseOptions { useNixOSFolder=baseDirectory } = do
+  createDirectoryIfMissing True $ toS toplevel
+  writeFile (toS glueModuleFile) glueModule
+  writeFile (toS cacheModuleFile) cacheModule
+  unless isPublic $ void $ addPrivateBinaryCacheNetRC maybeConfig bc NixConf.Global
+  putText instructions
+  where
+    configurationNix :: Text
+    configurationNix = toS $ toS baseDirectory </> "configuration.nix"
+    namespace :: Text
+    namespace = "cachix"
+    toplevel :: Text
+    toplevel = toS $ toS baseDirectory </> toS namespace
+    glueModuleFile :: Text
+    glueModuleFile = toplevel <> ".nix"
+    cacheModuleFile :: Text
+    cacheModuleFile = toplevel <> "/" <> toS name <> ".nix"
+
+    extra :: Text
+    extra = maybe "" (\user -> [i|trustedUsers = [ "root" "${user}" ];|]) maybeUser
+
+    instructions :: Text
+    instructions = [iTrim|
+Cachix configuration written to ${glueModuleFile}.
+Binary cache ${name} configuration written to ${cacheModuleFile}.
+
+To start using cachix add the following to your ${configurationNix}:
+
+    imports = [ ./cachix.nix ];
+
+Then run:
+
+    $ nixos-rebuild switch
+    |]
+
+    glueModule :: Text
+    glueModule = [i|
+# WARN: this file will get overwritten by $ cachix use <name>
+{ pkgs, lib, ... }:
+
+let
+  folder = ./cachix;
+  toImport = name: value: folder + ("/" + name);
+  filterCaches = key: value: value == "regular" && lib.hasSuffix ".nix" key;
+  imports = lib.mapAttrsToList toImport (lib.filterAttrs filterCaches (builtins.readDir folder));
+in {
+  inherit imports;
+  nix.binaryCaches = ["https://cache.nixos.org/"];
+}
+    |]
+
+    cacheModule :: Text
+    cacheModule = [i|
+{
+  nix = {
+    binaryCaches = [
+      "${uri}"
+    ];
+    binaryCachePublicKeys = [
+      ${T.intercalate " " (map (\s -> "\"" <> s <> "\"") publicSigningKeys)}
+    ];
+    ${extra}
+  };
+}
+    |]
+
+-- TODO: allow overriding netrc location
+addPrivateBinaryCacheNetRC :: Maybe Config -> Api.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>"
+    Just config -> do
+      let netrcfile = fromMaybe filename Nothing -- TODO: get netrc from nixconf
+      NetRc.add config [bc] netrcfile
+      putErrText $ "Configured private read access credentials in " <> toS filename
+      pure filename
 
 isTrustedUser :: [Text] -> IO Bool
 isTrustedUser users = do
diff --git a/src/Cachix/Client/NetRc.hs b/src/Cachix/Client/NetRc.hs
new file mode 100644
--- /dev/null
+++ b/src/Cachix/Client/NetRc.hs
@@ -0,0 +1,60 @@
+-- Deals with adding private caches to netrc
+module Cachix.Client.NetRc
+  ( add
+  ) where
+
+import qualified Data.ByteString    as BS
+import           Data.List            (nubBy)
+import qualified Data.Text          as T
+import           Network.NetRc
+import           Protolude
+import           System.Directory     ( doesFileExist )
+import           Servant.Auth.Client  (getToken)
+
+import qualified Cachix.Api as Api
+import           Cachix.Api.Error     (escalateAs)
+import           Cachix.Client.Config (Config, authToken)
+import           Cachix.Client.Exception (CachixException(NetRcParseError))
+
+
+-- | Add a list of binary caches to netrc under `filename`.
+--   Makes sure there are no duplicate entries (using domain as a key).
+--   If file under filename doesn't exist it's created.
+add :: Config
+    -> [Api.BinaryCache]
+    -> FilePath
+    -> IO ()
+add config binarycaches filename = do
+  doesExist <- doesFileExist filename
+  netrc <- if doesExist
+           then BS.readFile filename >>= parse
+           else return $ NetRc [] []
+  BS.writeFile filename $ netRcToByteString $ uniqueAppend 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) =
+      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 = NetRcHost
+      { nrhName = toS $ stripPrefix "http://" $ stripPrefix "https://" (Api.uri bc)
+      , nrhLogin = ""
+      , nrhPassword = getToken (authToken config)
+      , nrhAccount = ""
+      , nrhMacros = []
+      }
+      where
+        -- stripPrefix that either strips or returns the same string
+        stripPrefix :: Text -> Text -> Text
+        stripPrefix prefix str =
+          maybe str identity $ T.stripPrefix prefix str
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
@@ -29,10 +29,11 @@
   , defaultSigningKey
   , substitutersKey
   , trustedPublicKeysKey
+  , setNetRC
   ) where
 
 import Data.Char (isSpace)
-import Data.Text (unwords, unlines)
+import qualified Data.Text as T
 import Data.List (nub)
 import Protolude
 import qualified Text.Megaparsec as Mega
@@ -49,6 +50,7 @@
   = Substituters [Text]
   | TrustedUsers [Text]
   | TrustedPublicKeys [Text]
+  | NetRcFile Text
   | Other Text
   deriving (Show, Eq)
 
@@ -98,12 +100,13 @@
 defaultSigningKey = "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
 
 render :: NixVersion -> NixConf -> Text
-render nixversion (NixConf nixconflines) = unlines $ fmap go nixconflines
+render nixversion (NixConf nixconflines) = T.unlines $ fmap go nixconflines
   where
     go :: NixConfLine -> Text
-    go (Substituters xs) = substitutersKey nixversion <> " = " <> unwords xs
-    go (TrustedUsers xs) = "trusted-users = " <> unwords xs
-    go (TrustedPublicKeys xs) = trustedPublicKeysKey nixversion <> " = " <> unwords xs
+    go (Substituters xs) = substitutersKey nixversion <> " = " <> T.unwords xs
+    go (TrustedUsers xs) = "trusted-users = " <> T.unwords xs
+    go (TrustedPublicKeys xs) = trustedPublicKeysKey nixversion <> " = " <> T.unwords xs
+    go (NetRcFile filename) = "netrc-file = " <> filename
     go (Other line) = line
 
 substitutersKey :: NixVersion -> Text
@@ -132,7 +135,7 @@
     result <- parse <$> readFile filename
     case result of
       Left err -> do
-        putStrLn (Mega.parseErrorPretty err)
+        putStrLn (Mega.errorBundlePretty err)
         panic $ toS filename <> " failed to parse, please copy the above error and contents of nix.conf and open an issue at https://github.com/cachix/cachix"
       Right conf -> return $ Just conf
 
@@ -141,6 +144,11 @@
   nc <- f <$> read ncl
   write nixversion ncl nc
 
+setNetRC :: Text -> NixConf -> NixConf
+setNetRC netrc (NixConf nc) = NixConf $ filter noNetRc nc ++ [NetRcFile netrc]
+  where noNetRc (NetRcFile _) = False
+        noNetRc _ = True
+
 data NixConfLoc = Global | Local
   deriving (Show, Eq)
 
@@ -163,25 +171,27 @@
     _ <- many (char ' ')
     _ <- char '='
     _ <- many (char ' ')
-    values <- Mega.sepBy1 (many (satisfy (not . isSpace))) (some (char ' '))
+    values <- Mega.sepBy1 (many (Mega.satisfy (not . isSpace))) (some (char ' '))
     _ <- many spaceChar
     return $ constr (fmap toS values)
 
 parseOther :: Parser NixConfLine
-parseOther = Mega.try $ Other . toS <$> Mega.someTill anyChar (void eol <|> Mega.eof)
+parseOther = Mega.try $ Other . toS <$> Mega.someTill Mega.anySingle (void eol <|> Mega.eof)
 
 parseAltLine :: Parser NixConfLine
 parseAltLine =
-      (const (Other "")  <$> eol)
+      (Other "" <$ eol)
   <|> parseLine Substituters "substituters"
   <|> parseLine TrustedPublicKeys "trusted-public-keys"
   <|> parseLine TrustedUsers "trusted-users"
   <|> parseLine TrustedPublicKeys "binary-cache-public-keys"
   <|> parseLine Substituters "binary-caches"
+  -- NB: assume that space in this option means space in filename
+  <|> parseLine (NetRcFile . T.concat) "netrc-file"
   <|> parseOther
 
 parser :: Parser NixConf
 parser = NixConf <$> many parseAltLine
 
-parse :: Text -> Either (Mega.ParseError (Mega.Token Text) Void) NixConf
+parse :: Text -> Either (Mega.ParseErrorBundle Text Void) NixConf
 parse = Mega.parse parser "nix.conf"
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
@@ -2,6 +2,7 @@
 module Cachix.Client.OptionsParser
   ( CachixCommand(..)
   , CachixOptions(..)
+  , UseOptions(..)
   , BinaryCacheName
   , getOpts
   ) where
@@ -51,26 +52,45 @@
 data CachixCommand
   = AuthToken Text
   | Create BinaryCacheName
+  | GenerateKeypair BinaryCacheName
   | Push BinaryCacheName [Text] Bool -- TODO: refactor to a record
-  | Use BinaryCacheName Bool --- TODO: refactor to a record
+  | Use BinaryCacheName UseOptions
   | Version
   deriving Show
 
+data UseOptions = UseOptions
+  { useNixOS :: Bool
+  , useNixOSFolder :: FilePath
+  } deriving Show
 
 parserCachixCommand :: Parser CachixCommand
 parserCachixCommand = subparser $
   command "authtoken" (infoH authtoken (progDesc "Configure token for authentication to cachix.org")) <>
-  command "create" (infoH create (progDesc "Create a new binary cache")) <>
+  command "create" (infoH create (progDesc "DEPRECATED: Go to https://cachix.org instead")) <>
+  command "generate-keypair" (infoH generateKeypair (progDesc "Generate keypair for an 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"))
   where
+    nameArg = strArgument (metavar "NAME")
     authtoken = AuthToken <$> strArgument (metavar "TOKEN")
-    create = Create <$> strArgument (metavar "NAME")
-    push = Push <$> strArgument (metavar "NAME")
+    create = Create <$> nameArg
+    generateKeypair = GenerateKeypair <$> nameArg
+    push = Push <$> nameArg
                 <*> many (strArgument (metavar "PATHS..."))
-                <*> switch (long "watch-store" <> short 'w' <> help "Run in daemon mode and push store paths as they are added to /nix/store")
-    use = Use <$> strArgument (metavar "NAME")
-              <*> switch (long "nixos" <> short 'n' <> help "Output NixOS configuration lines")
+                <*> switch ( long "watch-store"
+                          <> short 'w'
+                          <> help "Run in daemon mode and push store paths as they are added to /nix/store"
+                           )
+    use = Use <$> nameArg
+              <*> (UseOptions <$> switch ( long "nixos"
+                                        <> short 'n'
+                                        <> help "Write NixOS modules")
+                              <*> strOption ( long "nixos-folder"
+                                           <> short 'd'
+                                           <> help "Base directory for NixOS configuration"
+                                           <> value "/etc/nixos/"
+                                           <> showDefault
+                                           ))
 
 getOpts :: IO (CachixOptions, CachixCommand)
 getOpts = do
diff --git a/test/NetRcSpec.hs b/test/NetRcSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/NetRcSpec.hs
@@ -0,0 +1,59 @@
+module NetRcSpec where
+
+import Paths_cachix
+import Protolude
+import System.Directory (copyFile)
+import System.IO.Temp   (withSystemTempFile)
+import Test.Hspec
+
+import           Cachix.Api (BinaryCache(..))
+import qualified Cachix.Client.NetRc as NetRc
+import           Cachix.Client.Config (Config(..))
+
+
+bc1 :: BinaryCache
+bc1 = BinaryCache
+ { name = "name"
+ , uri = "https://name.cachix.org"
+ , publicSigningKeys = ["pub"]
+ , isPublic = False
+ , githubUsername = "foobar"
+ }
+
+bc2 :: BinaryCache
+bc2 = BinaryCache
+  { name = "name2"
+  , uri = "https://name2.cachix.org"
+  , publicSigningKeys = ["pub2"]
+  , isPublic = False
+  , githubUsername = "foobar2"
+  }
+
+config :: Config
+config = Config
+  { authToken = "token123"
+  , binaryCaches = []
+  }
+
+-- TODO: poor man's golden tests, use https://github.com/stackbuilders/hspec-golden
+test :: [BinaryCache] -> Text -> Expectation
+test binaryCaches goldenName = withSystemTempFile "hspec-netrc" $ \filepath _ -> do
+    input <- getInput
+    output <- getOutput
+    copyFile input filepath
+    NetRc.add config binaryCaches filepath
+    real <- readFile filepath
+    expected <- readFile output
+    real `shouldBe` expected
+  where
+    getInput = getDataFileName $ toS goldenName <> ".input"
+    getOutput = getDataFileName $ toS goldenName <> ".output"
+
+spec :: Spec
+spec =
+  describe "add" $ do
+    -- TODO: not easy to test this with temp files as they are *created*
+    --it "populates non-existent netrc file" $ test [bc1, bc2] "fresh"
+    it "populates empty netrc file" $ test [bc1, bc2] "empty"
+    it "populates netrc file with one additional entry" $ test [bc2] "add"
+    it "populates netrc file with one overriden entry" $ test [bc2] "override"
diff --git a/test/NixConfSpec.hs b/test/NixConfSpec.hs
--- a/test/NixConfSpec.hs
+++ b/test/NixConfSpec.hs
@@ -20,6 +20,7 @@
 bc = BinaryCache
  { name = "name"
  , uri = "https://name.cachix.org"
+ , isPublic = True
  , publicSigningKeys = ["pub"]
  , githubUsername = "foobar"
  }
diff --git a/test/data/add.input b/test/data/add.input
new file mode 100644
--- /dev/null
+++ b/test/data/add.input
@@ -0,0 +1,1 @@
+machine name.cachix.org password token123
diff --git a/test/data/add.output b/test/data/add.output
new file mode 100644
--- /dev/null
+++ b/test/data/add.output
@@ -0,0 +1,3 @@
+machine name2.cachix.org password token123
+
+machine name.cachix.org password token123
diff --git a/test/data/empty.input b/test/data/empty.input
new file mode 100644
--- /dev/null
+++ b/test/data/empty.input
diff --git a/test/data/empty.output b/test/data/empty.output
new file mode 100644
--- /dev/null
+++ b/test/data/empty.output
@@ -0,0 +1,3 @@
+machine name.cachix.org password token123
+
+machine name2.cachix.org password token123
diff --git a/test/data/override.input b/test/data/override.input
new file mode 100644
--- /dev/null
+++ b/test/data/override.input
@@ -0,0 +1,3 @@
+machine name.cachix.org password token123
+
+machine name2.cachix.org password abc123
diff --git a/test/data/override.output b/test/data/override.output
new file mode 100644
--- /dev/null
+++ b/test/data/override.output
@@ -0,0 +1,3 @@
+machine name2.cachix.org password token123
+
+machine name.cachix.org password token123
