cachix-0.2.0: src/Cachix/Client/NixConf.hs
{- (Very limited) parser, rendered and modifier of nix.conf
Supports subset of nix.conf given Nix 2.0 or Nix 1.0
When reading config files, it normalizes Nix 1.0/2.0 names to unified naming,
then when it writes the config back, it uses naming depending what given Nix
version considers as recommended.
-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveFunctor #-}
module Cachix.Client.NixConf
( NixConf
, NixConfG(..)
, NixConfLine(..)
, NixConfLoc(..)
, render
, add
, read
, update
, write
, getFilename
, parser
, parse
, readLines
, writeLines
, isTrustedUsers
, defaultPublicURI
, defaultSigningKey
, substitutersKey
, trustedPublicKeysKey
, setNetRC
) where
import Data.Char (isSpace)
import qualified Data.Text as T
import Data.List (nub)
import Protolude
import qualified Text.Megaparsec as Mega
import Text.Megaparsec.Char
import System.Directory ( doesFileExist, createDirectoryIfMissing
, getXdgDirectory, XdgDirectory(..)
)
import System.FilePath.Posix ( takeDirectory )
import Cachix.Api (BinaryCache(..))
import Cachix.Client.NixVersion (NixVersion(..))
data NixConfLine
= Substituters [Text]
| TrustedUsers [Text]
| TrustedPublicKeys [Text]
| NetRcFile Text
| Other Text
deriving (Show, Eq)
newtype NixConfG a = NixConf a
deriving (Show, Eq, Functor)
type NixConf = NixConfG [NixConfLine]
readLines :: [NixConf] -> (NixConfLine -> Maybe [Text]) -> [Text]
readLines nixconfs predicate = concatMap f nixconfs
where
f (NixConf xs) = foldl foldIt [] xs
foldIt :: [Text] -> NixConfLine -> [Text]
foldIt prev new = prev <> fromMaybe [] (predicate new)
writeLines :: (NixConfLine -> Maybe [Text]) -> NixConfLine -> NixConf -> NixConf
writeLines predicate addition = fmap f
where
f x = filter (isNothing . predicate) x <> [addition]
isSubstituter :: NixConfLine -> Maybe [Text]
isSubstituter (Substituters xs) = Just xs
isSubstituter _ = Nothing
isPublicKey :: NixConfLine -> Maybe [Text]
isPublicKey (TrustedPublicKeys xs) = Just xs
isPublicKey _ = Nothing
isTrustedUsers :: NixConfLine -> Maybe [Text]
isTrustedUsers (TrustedUsers xs) = Just xs
isTrustedUsers _ = Nothing
-- | Pure version of addIO
add :: BinaryCache -> [NixConf] -> NixConf -> NixConf
add BinaryCache{..} 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) <> [uri]
publicKeys = (defaultSigningKey : readLines toRead isPublicKey) <> publicSigningKeys
defaultPublicURI :: Text
defaultPublicURI = "https://cache.nixos.org"
defaultSigningKey :: Text
defaultSigningKey = "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
render :: NixVersion -> NixConf -> Text
render nixversion (NixConf nixconflines) = T.unlines $ fmap go nixconflines
where
go :: NixConfLine -> Text
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
substitutersKey Nix1XX = "binary-caches"
substitutersKey Nix20 = "substituters"
substitutersKey Nix201 = "substituters"
trustedPublicKeysKey :: NixVersion -> Text
trustedPublicKeysKey Nix1XX = "binary-cache-public-keys"
trustedPublicKeysKey Nix20 = "trusted-public-keys"
trustedPublicKeysKey Nix201 = "trusted-public-keys"
write :: NixVersion -> NixConfLoc -> NixConf -> IO ()
write nixversion ncl nc = do
filename <- getFilename ncl
createDirectoryIfMissing True (takeDirectory filename)
writeFile filename $ render nixversion nc
read :: NixConfLoc -> IO (Maybe NixConf)
read ncl = do
filename <- getFilename ncl
doesExist <- doesFileExist filename
if not doesExist
then return Nothing
else do
result <- parse <$> readFile filename
case result of
Left err -> do
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
update :: NixVersion -> NixConfLoc -> (Maybe NixConf -> NixConf) -> IO ()
update nixversion ncl f = do
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)
getFilename :: NixConfLoc -> IO FilePath
getFilename ncl = do
dir <- case ncl of
Global -> return "/etc/nix"
Local -> getXdgDirectory XdgConfig "nix"
return $ dir <> "/nix.conf"
-- nix.conf Parser
type Parser = Mega.Parsec Void Text
-- TODO: handle comments
parseLine :: ([Text] -> NixConfLine) -> Text -> Parser NixConfLine
parseLine constr name = Mega.try $ do
_ <- optional (some (char ' '))
_ <- string name
_ <- many (char ' ')
_ <- char '='
_ <- many (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 Mega.anySingle (void eol <|> Mega.eof)
parseAltLine :: Parser NixConfLine
parseAltLine =
(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.ParseErrorBundle Text Void) NixConf
parse = Mega.parse parser "nix.conf"