{-# LANGUAGE BlockArguments #-} module Hercules.Agent.Cache where import Data.Map qualified as M import Data.Text qualified as T import Foreign.ForeignPtr (ForeignPtr, withForeignPtr) import Hercules.Agent.Cachix qualified as Cachix import Hercules.Agent.Config.BinaryCaches qualified as Config import Hercules.Agent.Env (App) import Hercules.Agent.Env qualified as Env import Hercules.Agent.Netrc qualified as Netrc import Hercules.Agent.Nix qualified as Nix import Hercules.CNix qualified as CNix import Hercules.CNix.Std.Set (StdSet, toListFP) import Hercules.CNix.Std.Set qualified as Std.Set import Hercules.CNix.Store (StorePath) import Hercules.CNix.Store qualified as Store import Hercules.Error (defaultRetry) import Hercules.Formats.NixCache qualified as NixCache import Katip import Protolude withCaches :: App a -> App a withCaches m = do netrcLns <- Cachix.getNetrcLines csubsts <- Cachix.getSubstituters cpubkeys <- Cachix.getTrustedPublicKeys nixCaches <- asks (Config.nixCaches . Env.binaryCaches) let substs = nixCaches & toList <&> NixCache.storeURI pubkeys = nixCaches & toList <&> NixCache.publicKeys & join netrcFile <- Netrc.getNetrcFile Netrc.appendLines netrcLns Nix.withExtraOptions [ ("netrc-file", toS netrcFile), ("substituters", T.intercalate " " (substs <> csubsts)), ("trusted-public-keys", T.intercalate " " (pubkeys <> cpubkeys)) ] m getConfiguredSubstituters :: App [Text] getConfiguredSubstituters = do nixCaches <- asks (Config.nixCaches . Env.binaryCaches) let substs = nixCaches & toList <&> NixCache.storeURI csubsts <- Cachix.getSubstituters pure (substs <> csubsts) push :: -- | Nix Store CNix.Store -> -- | Cache name Text -> -- | Paths, which do not have to form a closure [StorePath] -> -- | Number of concurrent upload threads Int -> App () push store cacheName paths concurrency = katipAddNamespace "Push" $ katipAddContext (sl "cacheName" cacheName) do caches <- asks Env.binaryCaches let maybeNix = Config.nixCaches caches & M.lookup cacheName & fmap \cache -> nixPush cache paths concurrency maybeCachix = Config.cachixCaches caches & M.lookup cacheName & fmap \_cache -> Cachix.push store cacheName paths concurrency failNothing = throwIO $ FatalError $ "Agent does not have a binary cache named " <> show cacheName <> " in its configuration." fromMaybe failNothing (maybeNix <|> maybeCachix) nixPush :: NixCache.NixCache -> [StorePath] -> Int -> App () nixPush cacheConf paths _concurrency = do keys <- liftIO $ for (NixCache.signingKeys cacheConf) (CNix.parseSecretKey . encodeUtf8) CNix.withStore $ \store -> do (Sum total, Sum signed) <- liftIO do pathSet <- Std.Set.fromListFP paths mconcat <$> for keys \key -> do signClosure store key pathSet katipAddContext (sl "num-signatures" signed <> sl "num-paths" total) $ logLocM DebugS "Signed" liftIO $ CNix.clearPathInfoCache store defaultRetry . CNix.withStoreFromURI (NixCache.storeURI cacheConf) $ \cache -> do liftIO (CNix.copyClosure store cache paths) signClosure :: CNix.Store -> ForeignPtr CNix.SecretKey -> StdSet Store.NixStorePath -> IO (Sum Int, Sum Int) signClosure store key' pathSet = withForeignPtr key' \key -> do closure <- Store.computeFSClosure store Store.defaultClosureParams pathSet >>= toListFP closure & traverse ( \path -> do CNix.signPath store key path ) <&> foldMap (\case True -> (1, 1); False -> (1, 0))