cachix-1.7: src/Cachix/Client/Daemon/PostBuildHook.hs
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TypeApplications #-}
module Cachix.Client.Daemon.PostBuildHook where
import Data.Containers.ListUtils (nubOrd)
import Data.String (String)
import Data.String.Here
import Protolude
import System.Directory
( XdgDirectory (XdgConfig),
XdgDirectoryList (XdgConfigDirs),
getXdgDirectory,
getXdgDirectoryList,
)
import System.Environment (getExecutablePath, lookupEnv)
import System.FilePath ((</>))
import System.IO.Temp (withSystemTempDirectory)
import System.Posix.Files
type EnvVar = (String, String)
modifyEnv :: EnvVar -> [EnvVar] -> [EnvVar]
modifyEnv (envName, envValue) processEnv =
nubOrd $ (envName, envValue) : processEnv
withSetup :: Maybe FilePath -> (FilePath -> EnvVar -> IO a) -> IO a
withSetup mdaemonSock f =
withSystemTempDirectory "cachix-daemon" $ \tempDir -> do
let postBuildHookScriptPath = tempDir </> "post-build-hook.sh"
postBuildHookConfigPath = tempDir </> "nix.conf"
daemonSock = fromMaybe (tempDir </> "daemon.sock") mdaemonSock
cachixBin <- getExecutablePath
writeFile postBuildHookScriptPath (postBuildHookScript cachixBin daemonSock)
setFileMode postBuildHookScriptPath 0o755
mnixConfEnv <- buildNixConfEnv postBuildHookScriptPath
nixUserConfFilesEnv <- buildNixUserConfFilesEnv postBuildHookConfigPath
nixConfEnvVar <- case mnixConfEnv of
Just nixConfEnv -> return nixConfEnv
Nothing -> do
writeFile postBuildHookConfigPath (postBuildHookConfig postBuildHookScriptPath)
return nixUserConfFilesEnv
f daemonSock nixConfEnvVar
-- | Build the NIX_CONF environment variable.
--
-- NIX_CONF completely overrides the nix.conf.
-- This is generally undesirable because the user and system nix.confs contain important settings, like substituters.
-- Therefore, this returns Nothing if NIX_CONF is not already set to allow fallback to NIX_USER_CONF_FILES.
buildNixConfEnv :: FilePath -> IO (Maybe EnvVar)
buildNixConfEnv postBuildHookScriptPath =
fmap appendNixConf <$> lookupEnv "NIX_CONF"
where
appendNixConf :: String -> EnvVar
appendNixConf conf =
( "NIX_CONF",
conf <> "\n" <> toS (postBuildHookConfig postBuildHookScriptPath)
)
-- | Build the NIX_USER_CONF_FILES environment variable.
--
-- From man nix.conf:
--
-- If NIX_USER_CONF_FILES is set, then each path separated by : will be loaded in reverse order.
--
-- Otherwise it will look for nix/nix.conf files in XDG_CONFIG_DIRS and XDG_CONFIG_HOME. If
-- unset, XDG_CONFIG_DIRS defaults to /etc/xdg, and XDG_CONFIG_HOME defaults to $HOME/.config
-- as per XDG Base Directory Specification.
--
-- We don't need to load the system config from $NIX_CONF_DIR/nix.conf.
-- Nix loads it by default and uses it as the base config.
buildNixUserConfFilesEnv :: FilePath -> IO EnvVar
buildNixUserConfFilesEnv nixConfPath = do
-- A user can set NIX_USER_CONF_FILES to override the default nix.conf files.
-- In that case, we reuse it and prepend our own config file.
mexistingEnv <- lookupEnv "NIX_USER_CONF_FILES"
newNixUserConfFiles <- case mexistingEnv of
Just existingEnv -> return $ nixConfPath <> ":" <> existingEnv
Nothing -> do
userConfigFiles <- getUserConfigFiles
-- Combine all the nix.conf paths into one string, separated by colons.
-- Filter out empty paths.
return $ intercalate ":" $ filter (not . null) $ nixConfPath : userConfigFiles
return ("NIX_USER_CONF_FILES", newNixUserConfFiles)
getUserConfigFiles :: IO [FilePath]
getUserConfigFiles =
fmap (</> "nix/nix.conf") <$> getUserConfigDirs
getUserConfigDirs :: IO [FilePath]
getUserConfigDirs = do
configHome <- getXdgDirectory XdgConfig empty
configDirs <- getXdgDirectoryList XdgConfigDirs
return $ configHome : configDirs
postBuildHookConfig :: FilePath -> Text
postBuildHookConfig scriptPath =
[iTrim|
post-build-hook = ${toS scriptPath :: Text}
|]
postBuildHookScript :: FilePath -> FilePath -> Text
postBuildHookScript cachixBin socketPath =
[iTrim|
\#!/bin/sh
\# set -eu
set -f # disable globbing
exec ${toS cachixBin :: Text} daemon push \\
--socket ${toS socketPath :: Text} \\
$OUT_PATHS
|]