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 +12/−0
- cachix.cabal +16/−8
- cachix/Main.hs +1/−0
- src/Cachix/Client.hs +9/−29
- src/Cachix/Client/Commands.hs +80/−69
- src/Cachix/Client/Config.hs +13/−12
- src/Cachix/Client/Env.hs +53/−0
- src/Cachix/Client/InstallationMode.hs +5/−3
- src/Cachix/Client/OptionsParser.hs +17/−6
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'