packages feed

cachix 0.1.1 → 0.1.2

raw patch · 9 files changed

+206/−127 lines, 9 filesdep +filepathdep +safe-exceptions

Dependencies added: filepath, safe-exceptions

Files

ChangeLog.md view
@@ -7,6 +7,18 @@  ## Unreleased +## [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
cachix.cabal view
@@ -1,11 +1,13 @@--- This file has been generated from package.yaml by hpack version 0.28.2.+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.31.0. -- -- see: https://github.com/sol/hpack ----- hash: ab3acbad7328ef095ad03032f8457a46c65d54e21216423686c3a91d85a7b79a+-- hash: 098d91889f12eca7fc2fd5f1197c1ec823f43b19fd5be38a630ea965ce99dae8  name:           cachix-version:        0.1.1+version:        0.1.2 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@@ -15,10 +17,9 @@ license:        Apache-2.0 license-file:   LICENSE build-type:     Simple-cabal-version:  >= 1.10 extra-source-files:-    ChangeLog.md     README.md+    ChangeLog.md  source-repository head   type: git@@ -29,6 +30,7 @@       Cachix.Client       Cachix.Client.Commands       Cachix.Client.Config+      Cachix.Client.Env       Cachix.Client.InstallationMode       Cachix.Client.NixConf       Cachix.Client.NixVersion@@ -39,7 +41,7 @@       Paths_cachix   hs-source-dirs:       src-  default-extensions: OverloadedStrings NoImplicitPrelude RecordWildCards+  default-extensions: OverloadedStrings NoImplicitPrelude RecordWildCards DeriveGeneric   ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -fwarn-tabs -fwarn-unused-imports -fwarn-missing-signatures -fwarn-name-shadowing -fwarn-incomplete-patterns   build-depends:       async@@ -56,6 +58,7 @@     , dhall     , directory     , ed25519+    , filepath     , fsnotify     , here     , http-client@@ -70,6 +73,7 @@     , process     , protolude     , resourcet+    , safe-exceptions     , servant >=0.14.1     , servant-auth     , servant-auth-client >=0.3.3.0@@ -89,7 +93,7 @@       Paths_cachix   hs-source-dirs:       cachix-  default-extensions: OverloadedStrings NoImplicitPrelude RecordWildCards+  default-extensions: OverloadedStrings NoImplicitPrelude RecordWildCards DeriveGeneric   ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -fwarn-tabs -fwarn-unused-imports -fwarn-missing-signatures -fwarn-name-shadowing -fwarn-incomplete-patterns -threaded -rtsopts -with-rtsopts=-N   build-depends:       async@@ -107,6 +111,7 @@     , dhall     , directory     , ed25519+    , filepath     , fsnotify     , here     , http-client@@ -121,6 +126,7 @@     , process     , protolude     , resourcet+    , safe-exceptions     , servant >=0.14.1     , servant-auth     , servant-auth-client >=0.3.3.0@@ -146,7 +152,7 @@       Paths_cachix   hs-source-dirs:       test-  default-extensions: OverloadedStrings NoImplicitPrelude RecordWildCards+  default-extensions: OverloadedStrings NoImplicitPrelude RecordWildCards DeriveGeneric   ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -fwarn-tabs -fwarn-unused-imports -fwarn-missing-signatures -fwarn-name-shadowing -fwarn-incomplete-patterns -threaded -rtsopts -with-rtsopts=-N   build-depends:       async@@ -164,6 +170,7 @@     , dhall     , directory     , ed25519+    , filepath     , fsnotify     , here     , hspec@@ -179,6 +186,7 @@     , process     , protolude     , resourcet+    , safe-exceptions     , servant >=0.14.1     , servant-auth     , servant-auth-client >=0.3.3.0
cachix/Main.hs view
@@ -21,5 +21,6 @@     handler :: SomeException -> IO a     handler e = do       hPutStrLn stderr ""+      -- TODO: pretty print the record once fixed https://github.com/haskell-servant/servant/issues/807       hPrint stderr e       exitFailure
src/Cachix/Client.hs view
@@ -2,40 +2,20 @@   ( main   ) where -import Data.Version                (showVersion)-import Paths_cachix                (version) import Protolude-import Network.HTTP.Simple         ( setRequestHeader )-import Network.HTTP.Client.TLS     ( newTlsManagerWith, tlsManagerSettings )-import Network.HTTP.Client         ( managerResponseTimeout, managerModifyRequest-                                   , responseTimeoutNone, ManagerSettings)-import Servant.Client              ( mkClientEnv) -import Cachix.Client.OptionsParser ( CachixCommand(..), CachixOptions(..), getOpts )-import Cachix.Client.Config        ( readConfig )+import Cachix.Client.OptionsParser ( CachixCommand(..), getOpts ) import Cachix.Client.Commands      as Commands-import Cachix.Client.URI           ( getBaseUrl)+import Cachix.Client.Env           ( mkEnv, cachixVersion )   main :: IO () main = do-  (CachixOptions{..}, command) <- getOpts-  config <- readConfig-  manager <- newTlsManagerWith customManagerSettings-  let env = mkClientEnv manager $ getBaseUrl host-  case command of -- TODO: might want readerT here with client, config and env and opts-    AuthToken token -> Commands.authtoken env config token-    Create name -> Commands.create env config name-    Push name paths watchStore -> Commands.push env config name paths watchStore-    Use name shouldEchoNixOS -> Commands.use env config name shouldEchoNixOS+  (cachixoptions, command) <- getOpts+  env <- mkEnv cachixoptions+  case command of+    AuthToken token -> Commands.authtoken env token+    Create name -> Commands.create env name+    Push name paths watchStore -> Commands.push env name paths watchStore+    Use name shouldEchoNixOS -> Commands.use env name shouldEchoNixOS     Version -> putText cachixVersion--customManagerSettings :: ManagerSettings-customManagerSettings = tlsManagerSettings-  { managerResponseTimeout = responseTimeoutNone-  -- managerModifyRequest :: Request -> IO Request-  , managerModifyRequest = return . setRequestHeader "User-Agent" [toS cachixVersion]-  }--cachixVersion :: Text-cachixVersion = "cachix " <> toS (showVersion version)
src/Cachix/Client/Commands.hs view
@@ -14,6 +14,7 @@ import qualified Control.Concurrent.QSem       as QSem import           Control.Concurrent.Async     (mapConcurrently) import           Control.Monad                (forever)+import           Control.Exception.Safe       (MonadThrow, throwM) import           Control.Monad.Trans.Resource (ResourceT) import qualified Data.ByteString.Base64        as B64 import           Data.Conduit@@ -37,22 +38,23 @@ import           System.Directory               ( doesFileExist ) import           System.FSNotify import           System.IO                      ( stdin, hIsTerminalDevice )-import           System.Process                 ( readProcessWithExitCode, readProcess )+import           System.Process                 ( readProcess ) import           System.Environment             ( lookupEnv ) import qualified Streaming.Prelude             as S  import qualified Cachix.Api                    as Api import           Cachix.Api.Signing             (fingerprint, passthroughSizeSink, passthroughHashSink)+import qualified Cachix.Types.NarInfoCreate    as Api import           Cachix.Client.Config           ( Config(..)                                                 , BinaryCacheConfig(..)                                                 , writeConfig                                                 , mkConfig                                                 ) import qualified Cachix.Client.Config          as Config+import           Cachix.Client.Env              ( Env(..) )+import           Cachix.Client.OptionsParser    ( CachixOptions(..) ) import           Cachix.Client.InstallationMode-import           Cachix.Client.NixVersion       ( getNixVersion-                                                , NixVersion-                                                )+import           Cachix.Client.NixVersion       ( getNixVersion ) import qualified Cachix.Client.NixConf         as NixConf import           Cachix.Client.Servant @@ -63,10 +65,10 @@ cachixBCClient :: Text -> Api.BinaryCacheAPI (AsClientT ClientM) cachixBCClient name = fromServant $ Api.cache cachixClient name -authtoken :: ClientEnv -> Maybe Config -> Text -> IO ()-authtoken _ maybeConfig token = do+authtoken :: Env -> Text -> IO ()+authtoken env token = do   -- TODO: check that token actually authenticates!-  writeConfig $ case maybeConfig of+  writeConfig (configPath (cachixoptions env)) $ case config env of     Just config -> config { authToken = token }     Nothing -> mkConfig token   putStrLn ([hereLit|@@ -77,13 +79,13 @@ and share it with others over https://<name>.cachix.org   |] :: Text) -create :: ClientEnv -> Maybe Config -> Text -> IO ()-create _ Nothing _ = throwIO $ NoConfig "start with: $ cachix authtoken <token>"-create env (Just config@Config{..}) name = do+create :: Env -> Text -> IO ()+create Env { config = Nothing } _ = throwIO $ NoConfig "start with: $ cachix authtoken <token>"+create env@Env { config = Just config } name = do   (PublicKey pk, SecretKey sk) <- createKeypair    let bc = Api.BinaryCacheCreate $ toS $ B64.encode pk-  res <- (`runClientM` env) $ Api.create (cachixBCClient name) (Token (toS authToken)) bc+  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@@ -91,8 +93,10 @@       -- write signing key to config       let signingKey = toS $ B64.encode sk           bcc = BinaryCacheConfig name signingKey-      writeConfig $ config { binaryCaches = binaryCaches <> [bcc] } +      writeConfig (configPath (cachixoptions env)) $+        config { binaryCaches = binaryCaches config <> [bcc] }+       putStrLn ([iTrim| Signing key has been saved on your local machine. To populate your binary cache:@@ -110,10 +114,10 @@        |] :: Text) -use :: ClientEnv -> Maybe Config -> Text -> Bool -> IO ()-use env _ name shouldEchoNixOS = do+use :: Env -> Text -> Bool -> IO ()+use env name shouldEchoNixOS = do   -- 1. get cache public key-  res <- (`runClientM` env) $ Api.get (cachixBCClient name)+  res <- (`runClientM` clientenv env) $ Api.get (cachixBCClient name)   case res of     -- TODO: handle 404     Left err -> panic $ show err@@ -139,8 +143,8 @@   -- TODO: lots of room for perfomance improvements-push :: ClientEnv -> Maybe Config -> Text -> [Text] -> Bool -> IO ()-push env config name rawPaths False = do+push :: Env -> Text -> [Text] -> Bool -> IO ()+push env name rawPaths False = do   hasNoStdin <- hIsTerminalDevice stdin   when (not hasNoStdin && not (null rawPaths)) $ throwIO $ AmbiguousInput "You provided both stdin and store path arguments, pick only one to proceed."   inputStorePaths <-@@ -153,12 +157,12 @@    -- Query list of paths   -- TODO: split args if too big-  (exitcode, out, err) <- readProcessWithExitCode "nix-store" (fmap toS (["-qR"] <> inputStorePaths)) mempty+  paths <- T.lines . toS <$> readProcess "nix-store" (fmap toS (["-qR"] <> inputStorePaths)) mempty    -- TODO: make pool size configurable, on beefier machines this could be doubled-  _ <- mapConcurrentlyBounded 4 (pushStorePath env config name) (T.lines (toS out))+  _ <- mapConcurrentlyBounded 4 (pushStorePath env name) paths   putText "All done."-push env config name _ True = withManager $ \mgr -> do+push env name _ True = withManager $ \mgr -> do   _ <- watchDir mgr "/nix/store" filterF action   putText "Watching /nix/store for new builds ..."   forever $ threadDelay 1000000@@ -169,7 +173,7 @@ #else     action (Removed fp _) = #endif-      pushStorePath env config name $ toS $ dropEnd 5 fp+      pushStorePath env name $ toS $ dropEnd 5 fp     action _ = return ()      filterF :: ActionPredicate@@ -184,12 +188,12 @@     dropEnd :: Int -> [a] -> [a]     dropEnd index xs = take (length xs - index) xs -pushStorePath :: ClientEnv -> Maybe Config -> Text -> Text -> IO ()-pushStorePath env config name storePath = do+pushStorePath :: Env -> Text -> Text -> IO ()+pushStorePath env name storePath = do   -- use secret key from config or env   maybeEnvSK <- lookupEnv "CACHIX_SIGNING_KEY"   let matches Config{..} = filter (\bc -> Config.name bc == name) binaryCaches-      maybeBCSK = case config of+      maybeBCSK = case config env of         Nothing -> Nothing         Just c -> Config.secretKey <$> head (matches c)       -- TODO: better error msg@@ -197,7 +201,7 @@       (storeHash, _) = splitStorePath $ toS storePath   -- Check if narinfo already exists   -- TODO: query also cache.nixos.org? server-side?-  res <- (`runClientM` env) $ Api.narinfoHead+  res <- (`runClientM` clientenv env) $ Api.narinfoHead     (cachixBCClient name)     (Api.NarInfoC storeHash)   case res of@@ -215,9 +219,10 @@         fileHashRef <- liftIO $ newIORef ("" :: Text)          -- stream store path as xz compressed nar file-        (ClosedStream, (source, close), ClosedStream, cph) <- streamingProcess $ shell ("nix-store --dump " <> toS storePath)+        let cmd = proc "nix-store" ["--dump", toS storePath]+        (ClosedStream, (stdoutStream, closeStdout), ClosedStream, cph) <- streamingProcess cmd         let stream'-              = source+              = stdoutStream              .| passthroughSizeSink narSizeRef              .| passthroughHashSink narHashRef              .| compress Nothing@@ -227,61 +232,67 @@             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 newEnv = env {-              baseUrl = (baseUrl env) { baseUrlHost = toS name <> "." <> baseUrlHost (baseUrl env)}+        let newClientEnv = (clientenv env) {+              baseUrl = (baseUrl (clientenv env)) { baseUrlHost = toS name <> "." <> baseUrlHost (baseUrl (clientenv env))}             }         -- TODO: http retry: retry package?-        res <- (`runClientM` newEnv) $ Api.createNar+        NoContent <- escalate <=< (`runClientM` newClientEnv) $ Api.createNar           (cachixBCClient name)           (contentType (Proxy :: Proxy Api.XNixNar), conduitToStreaming)-        close+        closeStdout         exitcode <- waitForStreamingProcess cph-        case res of-          Left err -> panic $ show err-          Right NoContent -> do-            narSize <- readIORef narSizeRef-            narHashB16 <- readIORef narHashRef-            fileHash <- readIORef fileHashRef-            fileSize <- readIORef fileSizeRef+        -- TODO: print process stderr?+        when (exitcode /= ExitSuccess) $+          panic $ "Failed with " <> show exitcode <> ": " <> show cmd -            -- TODO: #3: implement using pure haskell-            narHash <- ("sha256:" <>) . T.strip . toS <$> readProcess "nix-hash" ["--type", "sha256", "--to-base32", toS narHashB16] mempty+        narSize <- readIORef narSizeRef+        narHashB16 <- readIORef narHashRef+        fileHash <- readIORef fileHashRef+        fileSize <- readIORef fileSizeRef -            (exitcode, out, err) <- readProcessWithExitCode "nix-store" ["-q", "--deriver", toS storePath] mempty-            let deriverRaw = T.strip $ toS out-                deriver = if deriverRaw == "unknown-deriver"-                          then deriverRaw-                          else T.drop 11 deriverRaw+        -- TODO: #3: implement using pure haskell+        narHash <- ("sha256:" <>) . T.strip . toS <$> readProcess "nix-hash" ["--type", "sha256", "--to-base32", toS narHashB16] mempty -            (exitcode, out, err) <- readProcessWithExitCode "nix-store" ["-q", "--references", toS storePath] 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 -            let-                references = sort $ T.lines $ T.strip $ toS out-                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-                  }-            -- Upload narinfo with signature-            res <- (`runClientM` env) $ Api.createNarinfo-              (cachixBCClient name)-              (Api.NarInfoC storeHash)-              nic-            case res of-              Left err -> panic $ show err -- TODO: handle json errors-              Right NoContent -> return ()+        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+              } +        escalate $ Api.isNarInfoCreateValid nic++        -- Upload narinfo with signature+        NoContent <- escalate <=< (`runClientM` clientenv env) $ Api.createNarinfo+          (cachixBCClient name)+          (Api.NarInfoC storeHash)+          nic+        return ()++ -- Utils +-- TODO: should one error abort the whole pushing process? Or do a retry? Or keep going and then fail?+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
src/Cachix/Client/Config.hs view
@@ -1,10 +1,11 @@-{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveAnyClass #-} module Cachix.Client.Config   ( Config(..)   , BinaryCacheConfig(..)   , readConfig   , writeConfig+  , getDefaultFilename+  , ConfigPath   , mkConfig   ) where @@ -16,6 +17,7 @@                         ) import System.Posix.Files ( setFileMode, unionFileModes, ownerReadMode                           , ownerWriteMode )+import System.FilePath.Posix ( takeDirectory ) import Protolude  @@ -35,30 +37,29 @@   , binaryCaches = []   } -readConfig :: IO (Maybe Config)-readConfig = do-  filename <- getFilename+type ConfigPath = FilePath++readConfig :: ConfigPath -> IO (Maybe Config)+readConfig filename = do   doesExist <- doesFileExist filename   if doesExist   then Just <$> input auto (toS filename)   else return Nothing --getFilename :: IO FilePath-getFilename = do+getDefaultFilename :: IO FilePath+getDefaultFilename = do   dir <- getXdgDirectory XdgConfig "cachix"-  createDirectoryIfMissing True dir   return $ dir <> "/cachix.dhall" -writeConfig :: Config -> IO ()-writeConfig config = do-  filename <- getFilename+writeConfig :: ConfigPath -> Config -> IO ()+writeConfig filename config = do   let doc = prettyExpr $ embed inject config+  createDirectoryIfMissing True (takeDirectory filename)   writeFile filename $ show doc   assureFilePermissions filename   putStrLn $ "Written to " <> filename --- Does: chmod rw file+-- chmod rw filepath assureFilePermissions :: FilePath -> IO () assureFilePermissions fp =   setFileMode fp $ unionFileModes ownerReadMode ownerWriteMode
+ src/Cachix/Client/Env.hs view
@@ -0,0 +1,53 @@+module Cachix.Client.Env+  ( Env(..)+  , mkEnv+  , cachixVersion+  ) where++import Protolude++import Data.Version                (showVersion)+import Paths_cachix                (version)++import Network.HTTP.Simple         ( setRequestHeader )+import Network.HTTP.Client.TLS     ( newTlsManagerWith, tlsManagerSettings )+import Network.HTTP.Client         ( managerResponseTimeout, managerModifyRequest+                                   , responseTimeoutNone, ManagerSettings)+import Servant.Client              ( mkClientEnv, ClientEnv )+import System.Directory            ( canonicalizePath )++import Cachix.Client.Config        ( readConfig, Config )+import Cachix.Client.OptionsParser ( CachixOptions(..) )+import Cachix.Client.URI           ( getBaseUrl )+++data Env = Env+  { config :: Maybe Config+  , clientenv :: ClientEnv+  , cachixoptions :: CachixOptions+  }+++mkEnv :: CachixOptions -> IO Env+mkEnv rawcachixoptions = do+  -- make sure path to the config is passed as absolute to dhall logic+  canonicalConfigPath <- canonicalizePath (configPath rawcachixoptions)+  let cachixoptions = rawcachixoptions { configPath = canonicalConfigPath }+  config <- readConfig $ configPath cachixoptions+  manager <- newTlsManagerWith customManagerSettings+  let clientenv = mkClientEnv manager $ getBaseUrl (host cachixoptions)+  return Env+    { config = config+    , clientenv = clientenv+    , cachixoptions = cachixoptions+    }++customManagerSettings :: ManagerSettings+customManagerSettings = tlsManagerSettings+  { managerResponseTimeout = responseTimeoutNone+  -- managerModifyRequest :: Request -> IO Request+  , managerModifyRequest = return . setRequestHeader "User-Agent" [toS cachixVersion]+  }++cachixVersion :: Text+cachixVersion = "cachix " <> toS (showVersion version)
src/Cachix/Client/InstallationMode.hs view
@@ -52,7 +52,7 @@   | not isNixOS && isRoot = Install nixVersion NixConf.Global   | nixVersion /= Nix201 = Nix20RequiresSudo   | isTrusted = Install nixVersion NixConf.Local-  | not isTrusted = UntrustedRequiresSudo+  | otherwise = UntrustedRequiresSudo   -- | Add a Binary cache to nix.conf, print nixos config or fail@@ -61,6 +61,7 @@   putText [iTrim| nix = {   binaryCaches = [+    "https://cache.nixos.org/"     "${uri}"   ];   binaryCachePublicKeys = [@@ -104,11 +105,12 @@   user <- getUser   -- to detect single user installations   permissions <- getPermissions "/nix/store"-  unless (null groups) $ do+  let isTrusted = writable permissions || user `elem` users+  when (not (null groups) && not isTrusted) $ 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 $ writable permissions || user `elem` users+  return isTrusted   where     groups = filter (\u -> T.head u == '@') users 
src/Cachix/Client/OptionsParser.hs view
@@ -13,14 +13,16 @@ import URI.ByteString.QQ import Options.Applicative +import qualified Cachix.Client.Config as Config  data CachixOptions = CachixOptions   { host :: URIRef Absolute+  , configPath :: Config.ConfigPath   , verbose :: Bool   } deriving Show -parserCachixOptions :: Parser CachixOptions-parserCachixOptions = CachixOptions+parserCachixOptions :: Config.ConfigPath -> Parser CachixOptions+parserCachixOptions defaultConfigPath = CachixOptions   <$> option uriOption ( long "host"                        <> short 'h'                        <> value [uri|https://cachix.org|]@@ -28,6 +30,13 @@                        <> showDefaultWith (toS . serializeURIRef')                        <> help "Host to connect to"                        )+ <*> strOption ( long "config"+              <> short 'c'+              <> value defaultConfigPath+              <> metavar "CONFIGPATH"+              <> showDefault+              <> help "Cachix configuration file"+              )   <*> switch ( long "verbose"              <> short 'v'              <> help "Verbose mode"@@ -64,11 +73,13 @@               <*> switch (long "nixos" <> short 'n' <> help "Output NixOS configuration lines")  getOpts :: IO (CachixOptions, CachixCommand)-getOpts = customExecParser (prefs showHelpOnEmpty) opts+getOpts = do+  configpath <- Config.getDefaultFilename+  customExecParser (prefs showHelpOnEmpty) (opts configpath) -opts :: ParserInfo (CachixOptions, CachixCommand)-opts = infoH parser desc-  where parser = (,) <$> parserCachixOptions <*> (parserCachixCommand <|> versionParser)+opts :: Config.ConfigPath -> ParserInfo (CachixOptions, CachixCommand)+opts configpath = infoH parser desc+  where parser = (,) <$> parserCachixOptions configpath <*> (parserCachixCommand <|> versionParser)         versionParser :: Parser CachixCommand         versionParser = flag' Version ( long "version"                      <> short 'V'