packages feed

hercules-ci-agent 0.9.1 → 0.9.2

raw patch · 16 files changed

+243/−218 lines, 16 filesdep ~hercules-ci-api-agentnew-component:exe:hercules-ci-nix-daemonPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: hercules-ci-api-agent

API changes (from Hackage documentation)

- Hercules.Agent.WorkerProtocol.Command.StartDaemon: StartDaemon :: FilePath -> StartDaemon
- Hercules.Agent.WorkerProtocol.Command.StartDaemon: [socketPath] :: StartDaemon -> FilePath
- Hercules.Agent.WorkerProtocol.Command.StartDaemon: data StartDaemon
- Hercules.Agent.WorkerProtocol.Command.StartDaemon: instance Data.Binary.Class.Binary Hercules.Agent.WorkerProtocol.Command.StartDaemon.StartDaemon
- Hercules.Agent.WorkerProtocol.Command.StartDaemon: instance GHC.Classes.Eq Hercules.Agent.WorkerProtocol.Command.StartDaemon.StartDaemon
- Hercules.Agent.WorkerProtocol.Command.StartDaemon: instance GHC.Generics.Generic Hercules.Agent.WorkerProtocol.Command.StartDaemon.StartDaemon
- Hercules.Agent.WorkerProtocol.Command.StartDaemon: instance GHC.Show.Show Hercules.Agent.WorkerProtocol.Command.StartDaemon.StartDaemon
- Hercules.Agent.WorkerProtocol.Event.DaemonStarted: DaemonStarted :: Bool -> DaemonStarted
- Hercules.Agent.WorkerProtocol.Event.DaemonStarted: [_dummy] :: DaemonStarted -> Bool
- Hercules.Agent.WorkerProtocol.Event.DaemonStarted: data DaemonStarted
- Hercules.Agent.WorkerProtocol.Event.DaemonStarted: instance Data.Binary.Class.Binary Hercules.Agent.WorkerProtocol.Event.DaemonStarted.DaemonStarted
- Hercules.Agent.WorkerProtocol.Event.DaemonStarted: instance GHC.Classes.Eq Hercules.Agent.WorkerProtocol.Event.DaemonStarted.DaemonStarted
- Hercules.Agent.WorkerProtocol.Event.DaemonStarted: instance GHC.Generics.Generic Hercules.Agent.WorkerProtocol.Event.DaemonStarted.DaemonStarted
- Hercules.Agent.WorkerProtocol.Event.DaemonStarted: instance GHC.Show.Show Hercules.Agent.WorkerProtocol.Event.DaemonStarted.DaemonStarted
+ Hercules.Agent.WorkerProcess: getDaemonExe :: MonadIO m => m [Char]
+ Hercules.Agent.WorkerProtocol.Event.AttributeError: [trace] :: AttributeError -> Maybe Text
- Hercules.Agent.WorkerProtocol.Event.AttributeError: AttributeError :: [ByteString] -> Text -> Maybe Text -> Maybe Text -> AttributeError
+ Hercules.Agent.WorkerProtocol.Event.AttributeError: AttributeError :: [ByteString] -> Text -> Maybe Text -> Maybe Text -> Maybe Text -> AttributeError

Files

CHANGELOG.md view
@@ -5,6 +5,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.2] - 2022-03-30++### Added++ - Separate traces in the dashboard (as in `--show-trace`)++### Fixed++ - Effects: `error: cannot open connection to remote store 'daemon': error: reading from file: Connection reset by peer`+ ## [0.9.1] - 2022-03-18  ### Added@@ -543,6 +553,7 @@  - Initial release +[0.9.2]: https://github.com/hercules-ci/hercules-ci-agent/compare/hercules-ci-agent-0.9.1...hercules-ci-agent-0.9.2 [0.9.1]: https://github.com/hercules-ci/hercules-ci-agent/compare/hercules-ci-agent-0.9.0...hercules-ci-agent-0.9.1 [0.9.0]: https://github.com/hercules-ci/hercules-ci-agent/compare/hercules-ci-agent-0.8.7...hercules-ci-agent-0.9.0 [0.8.7]: https://github.com/hercules-ci/hercules-ci-agent/compare/hercules-ci-agent-0.8.6...hercules-ci-agent-0.8.7
hercules-ci-agent-worker/Hercules/Agent/Worker.hs view
@@ -32,6 +32,7 @@ import Data.UUID (UUID) import Data.Vector (Vector) import qualified Data.Vector as V+import Foreign (alloca, peek) import Hercules.API.Agent.Evaluate.EvaluateEvent.OnPushHandlerEvent (OnPushHandlerEvent (OnPushHandlerEvent)) import qualified Hercules.API.Agent.Evaluate.EvaluateEvent.OnPushHandlerEvent import qualified Hercules.API.Agent.Evaluate.EvaluateTask as EvaluateTask@@ -56,7 +57,6 @@ import Hercules.Agent.Worker.HerculesStore (nixStore, setBuilderCallback, withHerculesStore) import Hercules.Agent.Worker.HerculesStore.Context (HerculesStore) import Hercules.Agent.Worker.Logging (withKatip)-import Hercules.Agent.Worker.NixDaemon (nixDaemon) import Hercules.Agent.WorkerProtocol.Command   ( Command,   )@@ -87,6 +87,7 @@ import qualified Hercules.CNix.Std.Vector as Std.Vector import Hercules.CNix.Store.Context (NixStorePathWithOutputs) import Hercules.CNix.Util (installDefaultSigINTHandler)+import Hercules.CNix.Verbosity (setShowTrace) import Hercules.Error import Hercules.UserException (UserException (UserException)) import Katip@@ -106,6 +107,7 @@ C.context (C.cppCtx <> C.fptrCtx)  C.include "<nix/error.hh>"+C.include "<nix/util.hh>" C.include "<iostream>" C.include "<sstream>" @@ -130,14 +132,12 @@ main = do   hSetBuffering stderr LineBuffering   Hercules.CNix.Expr.init+  setShowTrace True   _ <- installHandler sigTERM (Catch $ raiseSignal sigINT) Nothing   installDefaultSigINTHandler   Logger.initLogger   args <- Environment.getArgs   case args of-    ["nix-daemon", options] -> do-      setOptions options-      nixDaemon     [options] -> taskWorker options     _ -> throwIO $ FatalError "worker: Unrecognized command line arguments" @@ -192,7 +192,7 @@                 runCommand st ch command             )               `safeLiftedCatch` ( \e -> liftIO $ do-                                    e' <- renderException e+                                    (e', _trace) <- renderException e                                     writeChan ch (Just $ Exception e')                                     exitFailure                                 )@@ -223,26 +223,41 @@         pure x     ) -renderException :: SomeException -> IO Text+renderException :: SomeException -> IO (Text, Maybe Text) renderException e | Just (C.CppStdException ex _msg _ty) <- fromException e = renderStdException ex renderException e   | Just (C.CppNonStdException _ex maybeType) <- fromException e =-    pure $ "Unexpected C++ exception" <> foldMap (\t -> " of type " <> decodeUtf8With lenientDecode t) maybeType-renderException e | Just (FatalError msg) <- fromException e = pure msg-renderException e = pure $ toS $ displayException e+    pure ("Unexpected C++ exception" <> foldMap (\t -> " of type " <> decodeUtf8With lenientDecode t) maybeType, Nothing)+renderException e | Just (FatalError msg) <- fromException e = pure (msg, Nothing)+renderException e = pure (toS $ displayException e, Nothing) -renderStdException :: C.CppExceptionPtr -> IO Text-renderStdException e =-  [C.throwBlock| char * {+renderStdException :: C.CppExceptionPtr -> IO (Text, Maybe Text)+renderStdException e = alloca \traceStrPtr -> do+  msg <-+    [C.throwBlock| char * {+    char **traceStrPtr = $(char **traceStrPtr);+    *traceStrPtr = nullptr;     std::string r;     std::exception_ptr *e = $fptr-ptr:(std::exception_ptr *e);     try {       std::rethrow_exception(*e);     } catch (const nix::Error &e) {-      // r = e.what();-      std::stringstream s;-      nix::showErrorInfo(s, e.info(), true);-      r = s.str();+      {+        std::stringstream s;+        nix::showErrorInfo(s, e.info(), false);+        r = s.str();+      }+      {+        std::stringstream s;+        nix::showErrorInfo(s, e.info(), true);+        std::string t = s.str();+        // starts with r?+        if (t.rfind(r, 0) == 0) {+          t.replace(0, r.size(), "");+          t = nix::trim(t);+        }+        *traceStrPtr = strdup(t.c_str());+      }     } catch (const std::exception &e) {       r = e.what();     } catch (...) {@@ -252,8 +267,12 @@      return strdup(r.c_str());   }|]-    >>= unsafePackMallocCString-    <&> decodeUtf8With lenientDecode+      >>= unsafePackMallocCString+      <&> decodeUtf8With lenientDecode+  traceText <-+    peek traceStrPtr >>= traverseNonNull \s ->+      unsafePackMallocCString s <&> decodeUtf8With lenientDecode+  pure (msg, traceText)  connectCommand ::   (MonadUnliftIO m, KatipContext m, MonadThrow m) =>@@ -291,7 +310,7 @@                   runConduitRes                     ( Data.Conduit.handleC                         ( \e -> do-                            yield . Event.Error =<< liftIO (renderException e)+                            yield . Event.Error . fst =<< liftIO (renderException e)                             liftIO $ throwTo mainThread e                         )                         ( do@@ -453,17 +472,19 @@                 <> ", which is required during evaluation."                 <> foldMap (" " <>) (buildExceptionDetail e'),             AttributeError.errorDerivation = Just (buildExceptionDerivationPath e'),-            AttributeError.errorType = Just "BuildException"+            AttributeError.errorType = Just "BuildException",+            AttributeError.trace = Nothing -- would be nice to get a trace here. Throw and catch more C++ exception types?           } yieldAttributeError path e = do-  e' <- liftIO $ renderException e+  (e', maybeTrace) <- liftIO $ renderException e   yield $     Event.AttributeError $       AttributeError.AttributeError         { AttributeError.path = path,           AttributeError.message = e',           AttributeError.errorDerivation = Nothing,-          AttributeError.errorType = Just (show (typeOf e))+          AttributeError.errorType = Just (show (typeOf e)),+          AttributeError.trace = maybeTrace         }  maybeThrowBuildException :: MonadIO m => BuildResult.BuildStatus -> Text -> m ()
hercules-ci-agent-worker/Hercules/Agent/Worker/Build/Logger/Context.hs view
@@ -1,8 +1,14 @@ {-# LANGUAGE EmptyDataDecls #-} {-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TemplateHaskellQuotes #-} -module Hercules.Agent.Worker.Build.Logger.Context where+module Hercules.Agent.Worker.Build.Logger.Context+  ( Fields,+    HerculesLoggerEntry,+    LogEntryQueue,+    context,+  )+where  import qualified Data.Map as M import qualified Language.C.Inline.Context as C
hercules-ci-agent-worker/Hercules/Agent/Worker/Build/Prefetched.hs view
@@ -8,10 +8,15 @@ -- easy to maintain approach to do decouple it with inline-c-cpp. Perhaps it's -- better to use an FFI generator instead? -module Hercules.Agent.Worker.Build.Prefetched where+module Hercules.Agent.Worker.Build.Prefetched+  ( buildDerivation,+    Hercules.Agent.Worker.Build.Prefetched.getDerivation,+    BuildResult (..),+  )+where  import qualified Data.ByteString.Char8 as C8-import Foreign (FinalizerPtr, ForeignPtr, alloca, newForeignPtr, nullPtr, peek)+import Foreign (alloca, peek) import Foreign.C (peekCString) import Hercules.CNix.Encapsulation import Hercules.CNix.Store@@ -91,10 +96,6 @@     errorMessage :: Text   }   deriving (Show)--nullableForeignPtr :: FinalizerPtr a -> Ptr a -> IO (Maybe (ForeignPtr a))-nullableForeignPtr _ rawPtr | rawPtr == nullPtr = pure Nothing-nullableForeignPtr finalize rawPtr = Just <$> newForeignPtr finalize rawPtr  getDerivation :: Store -> StorePath -> IO (Maybe Derivation) getDerivation (Store store) derivationPath =
hercules-ci-agent-worker/Hercules/Agent/Worker/HerculesStore.hs view
@@ -3,8 +3,11 @@ {-# LANGUAGE TemplateHaskell #-}  module Hercules.Agent.Worker.HerculesStore-  ( module Hercules.Agent.Worker.HerculesStore,+  ( withHerculesStore,     HerculesStore,+    nixStore,+    printDiagnostics,+    setBuilderCallback,   ) where 
hercules-ci-agent-worker/Hercules/Agent/Worker/HerculesStore/Context.hs view
@@ -1,8 +1,13 @@ {-# LANGUAGE EmptyDataDecls #-} {-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TemplateHaskellQuotes #-} -module Hercules.Agent.Worker.HerculesStore.Context where+module Hercules.Agent.Worker.HerculesStore.Context+  ( context,+    HerculesStore,+    ExceptionPtr,+  )+where  import qualified Data.Map as M import Hercules.CNix.Expr.Context (Ref)
− hercules-ci-agent-worker/Hercules/Agent/Worker/NixDaemon.hs
@@ -1,123 +0,0 @@-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE TemplateHaskell #-}--module Hercules.Agent.Worker.NixDaemon where--import Control.Concurrent.STM.TVar (modifyTVar, newTVarIO, readTVar)-import qualified Data.Binary-import qualified Data.Binary.Get-import qualified Data.ByteString as BS-import qualified Data.ByteString.Lazy as BL-import qualified Data.Map as M-import Hercules.Agent.Binary (decodeBinaryFromHandle)-import qualified Hercules.Agent.WorkerProtocol.Command.StartDaemon as StartDaemon-import Hercules.Agent.WorkerProtocol.Event.DaemonStarted (DaemonStarted (DaemonStarted, _dummy))-import qualified Hercules.CNix as CNix-import qualified Language.C.Inline.Cpp as C-import qualified Language.C.Inline.Cpp.Exception as C-import Network.Socket-import Protolude-import System.Posix.Internals (setNonBlockingFD)-import UnliftIO (BufferMode (NoBuffering), hSetBuffering, timeout)-import UnliftIO.IO (hFlush)--C.context C.cppCtx--C.include "<nix/config.h>"-C.include "<nix/daemon.hh>"-C.include "<iostream>"-C.using "namespace nix"--nixDaemon :: IO ()--- nixDaemon = withKatip $ Logger.withLoggerConduit (_) $ Logger.withTappedStderr Logger.tapper $ liftIO $ do-nixDaemon = do-  hSetBuffering stdin NoBuffering-  startCmd <--    getStdinMessage >>= \case-      Left _ -> throwIO $ FatalError "Could not decode command"-      Right Nothing -> do-        putErrText "warning: Exit before receiving start command"-        exitSuccess -- We're done.-      Right (Just r) -> pure r-  let path = StartDaemon.socketPath startCmd--  CNix.init--  clientThreads <- newTVarIO mempty--  sock <- socket AF_UNIX Stream 0-  bind sock (SockAddrUnix path)-  listen sock 100--  putStdoutMessage DaemonStarted {_dummy = True}--  let socketLoop =-        forever do-          (clientSocket, _) <- accept sock-          (pid, _uid, _gid) <- getPeerCredential clientSocket-          uninterruptibleMask \_unmask -> do-            let removeMe = do-                  t <- myThreadId-                  atomically do-                    modifyTVar clientThreads (M.delete t)-            t <--              forkFinally -- TODO forkOS?-                (handleClient clientSocket)-                \case-                  Left _e -> removeMe -- >> putErrText ("Connection for pid " <> showPid pid <> " ended: " <> toS (displayException _e))-                  Right _ -> removeMe-            atomically do-              modifyTVar clientThreads (M.insert t pid)-  withAsync socketLoop \_socketLoopAsync ->-    void getStdinMessage--  void $ timeout (10 * 60 * 1000 * 1000) do-    hadThreads <- do-      ts <- atomically do-        readTVar clientThreads-      let hasThreads = not (null ts)-      when hasThreads do-        putErrLn ("Waiting for termination of connections from pids " <> unwords (showPid <$> toList ts))-      pure hasThreads-    atomically do-      ts <- readTVar clientThreads-      guard $ null ts-    when hadThreads do-      putErrText "All connections terminated; exiting"--showPid :: Maybe C.CUInt -> Text-showPid = maybe "unknown pid" show--putStdoutMessage :: DaemonStarted -> IO ()-putStdoutMessage msg = do-  BS.hPut stdout $ BL.toStrict $ Data.Binary.encode msg-  hFlush stdout--getStdinMessage ::-  IO-    ( Either-        (ByteString, Data.Binary.Get.ByteOffset, [Char])-        (Maybe StartDaemon.StartDaemon)-    )-getStdinMessage = decodeBinaryFromHandle stdin--handleClient :: Socket -> IO ()-handleClient clientSocket = withFdSocket clientSocket \fd -> flip finally (close clientSocket) do-  setNonBlockingFD fd False-  [C.throwBlock| void {-    ref<Store> store = openStore();-    FdSource from($(int fd));-    FdSink to($(int fd));-    daemon::TrustedFlag trusted = daemon::NotTrusted;-    daemon::RecursiveFlag recursive = daemon::NotRecursive;-    std::function<void(Store &)> authHook = [](Store &){};-    daemon::processConnection(-        store-        , from-        , to-        , trusted-        , recursive-        , authHook-        );-  }|]
hercules-ci-agent.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.4  name:           hercules-ci-agent-version:        0.9.1+version:        0.9.2 synopsis:       Runs Continuous Integration tasks on your machines category:       Nix, CI, Testing, DevOps homepage:       https://docs.hercules-ci.com@@ -90,12 +90,10 @@       Hercules.Agent.WorkerProtocol.Command.BuildResult       Hercules.Agent.WorkerProtocol.Command.Effect       Hercules.Agent.WorkerProtocol.Command.Eval-      Hercules.Agent.WorkerProtocol.Command.StartDaemon       Hercules.Agent.WorkerProtocol.Event       Hercules.Agent.WorkerProtocol.Event.Attribute       Hercules.Agent.WorkerProtocol.Event.AttributeError       Hercules.Agent.WorkerProtocol.Event.BuildResult-      Hercules.Agent.WorkerProtocol.Event.DaemonStarted       Hercules.Agent.WorkerProtocol.LogSettings       Hercules.Agent.WorkerProtocol.Orphans       Hercules.Effect@@ -135,6 +133,7 @@     , lifted-base     , monad-control     , mtl+    , network     , network-uri     , protolude >= 0.3     , process@@ -218,7 +217,7 @@     , filepath     , hercules-ci-agent     , hercules-ci-api-core == 0.1.4.0-    , hercules-ci-api-agent == 0.4.3.0+    , hercules-ci-api-agent == 0.4.4.0     , hostname     , http-client     , http-client-tls@@ -272,7 +271,6 @@       Hercules.Agent.Worker.HerculesStore       Hercules.Agent.Worker.HerculesStore.Context       Hercules.Agent.Worker.Logging-      Hercules.Agent.Worker.NixDaemon   hs-source-dirs:       hercules-ci-agent-worker @@ -398,3 +396,13 @@     , transformers-base     , unliftio-core   default-language: Haskell2010++executable hercules-ci-nix-daemon+  import: cxx-opts+  main-is: main.cc+  cxx-sources: hercules-ci-nix-daemon/daemon.cc+  default-language: Haskell2010+  hs-source-dirs: hercules-ci-nix-daemon+  pkgconfig-depends:+      nix-store >= 2.0+    , nix-main >= 2.0
hercules-ci-agent/Hercules/Agent/Evaluate.hs view
@@ -497,7 +497,8 @@                       { AttributeErrorEvent.expressionPath = decode <$> WorkerAttributeError.path e,                         AttributeErrorEvent.errorMessage = WorkerAttributeError.message e,                         AttributeErrorEvent.errorType = WorkerAttributeError.errorType e,-                        AttributeErrorEvent.errorDerivation = WorkerAttributeError.errorDerivation e+                        AttributeErrorEvent.errorDerivation = WorkerAttributeError.errorDerivation e,+                        AttributeErrorEvent.trace = WorkerAttributeError.trace e                       }                 continue               Event.EvaluationDone ->
+ hercules-ci-nix-daemon/daemon.cc view
@@ -0,0 +1,112 @@+#include <sys/wait.h>+#include <sys/socket.h>+#include <sys/un.h>+#include <unistd.h>++#include <string_view>++#include <nix/config.h>+#include <nix/shared.hh>+#include <nix/error.hh>+#include <nix/util.hh>+#include <nix/globals.hh>+#include <nix/store-api.hh>+#include <nix/daemon.hh>++using namespace nix;++static void sigChldHandler(int sigNo)+{+    // Ensure we don't modify errno of whatever we've interrupted+    auto saved_errno = errno;+    //  Reap all dead children.+    while (waitpid(-1, 0, WNOHANG) > 0) ;+    errno = saved_errno;+}++static void setSigChldAction()+{+    struct sigaction act, oact;+    act.sa_handler = sigChldHandler;+    sigfillset(&act.sa_mask);+    act.sa_flags = 0;+    if (sigaction(SIGCHLD, &act, &oact))+        throw SysError("setting SIGCHLD handler");+}++extern "C" int main(int argc, char **argv) {+    nix::initNix();+    +    for (int i = 1; i < argc && argv[i]; i++) {+        std::string arg(argv[i]);+        if (arg == "--option") {+            if (i + 2 < argc) {+                nix::globalConfig.set(argv[i+1], argv[i+2]);+                i += 2;+            }+            else {+                std::cerr << "Not enough arguments to --option" << std::endl;+                return 1;+            }+        }+    }++    // See withNixDaemonProxy for stdin (ab)use+    AutoCloseFD socketFD = dup(0);+    closeOnExec(socketFD.get());++    // Put /dev/null on stdin+    int devnull = open("/dev/null", O_RDONLY);+    dup2(devnull, 0);+    close(devnull);++    setSigChldAction();+    while (1) {+        struct sockaddr_un remoteAddr;+        socklen_t remoteAddrLen = sizeof(remoteAddr);+        try {+            AutoCloseFD remote = accept(socketFD.get(), (struct sockaddr *) &remoteAddr, &remoteAddrLen);+            checkInterrupt();+            if (!remote) {+                if (errno == EINTR) continue;+                else throw SysError("hercules-ci-nix-daemon: accepting connection");+            }+            closeOnExec(remote.get());+            ProcessOptions options;+            options.errorPrefix = "hercules-ci-nix-daemon: unexpected error: ";+            options.dieWithParent = false;+            options.runExitHandlers = true;+            options.allowVfork = false;+            startProcess([&]() {+                socketFD = -1;++                // Regular nix daemon creates a new session for connections and+                // does not kill connection child processes. Those are not+                // appropriate behaviors for a temporary proxy.++                FdSource from(remote.get());+                FdSink to(remote.get());+                // TODO: disable caching without interfering with user parameters+                ref<Store> store = openStore();+                daemon::TrustedFlag trusted = daemon::NotTrusted;+                daemon::RecursiveFlag recursive = daemon::NotRecursive;+                std::function<void(Store &)> authHook = [](Store &){};+                daemon::processConnection(+                    store+                    , from+                    , to+                    , trusted+                    , recursive+                    , authHook+                    );+            });+        } catch (Interrupted & e) {+            return 0;+        } catch (Error & error) {+            ErrorInfo ei = error.info();+            ei.msg = hintfmt("hercules-ci-nix-daemon: error processing connection: %1%", ei.msg.str());+            logError(ei);+        }+    }+    return 0;+}
+ hercules-ci-nix-daemon/main.cc view
@@ -0,0 +1,4 @@+/*+  main() is in daemon.cc, because cabal doesn't pass+  the correct flags to the main-is file.+*/
src/Hercules/Agent/WorkerProcess.hs view
@@ -1,6 +1,7 @@ module Hercules.Agent.WorkerProcess   ( runWorker,     getWorkerExe,+    getDaemonExe,     WorkerEnvSettings (..),     prepareEnv,     modifyEnv,@@ -65,6 +66,10 @@ getWorkerExe :: MonadIO m => m [Char] getWorkerExe = do   liftIO getBinDir <&> (</> "hercules-ci-agent-worker")++getDaemonExe :: MonadIO m => m [Char]+getDaemonExe = do+  liftIO getBinDir <&> (</> "hercules-ci-nix-daemon")  -- | Control a child process by communicating over stdin and stdout -- using a 'Binary' interface.
− src/Hercules/Agent/WorkerProtocol/Command/StartDaemon.hs
@@ -1,11 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}--module Hercules.Agent.WorkerProtocol.Command.StartDaemon where--import Data.Binary-import Protolude--data StartDaemon = StartDaemon-  { socketPath :: FilePath-  }-  deriving (Generic, Binary, Show, Eq)
src/Hercules/Agent/WorkerProtocol/Event/AttributeError.hs view
@@ -10,6 +10,7 @@   { path :: [ByteString],     message :: Text,     errorType :: Maybe Text,-    errorDerivation :: Maybe Text+    errorDerivation :: Maybe Text,+    trace :: Maybe Text   }   deriving (Generic, Binary, Show, Eq)
− src/Hercules/Agent/WorkerProtocol/Event/DaemonStarted.hs
@@ -1,10 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}--module Hercules.Agent.WorkerProtocol.Event.DaemonStarted where--import Data.Binary-import Protolude--data DaemonStarted = DaemonStarted-  {_dummy :: Bool {- make its binary representation non-empty -}}-  deriving (Generic, Binary, Show, Eq)
src/Hercules/Effect.hs view
@@ -7,16 +7,11 @@ import Control.Monad.Catch (MonadThrow) import qualified Data.Aeson as A import qualified Data.ByteString as BS-import qualified Data.ByteString.Char8 as C8 import qualified Data.ByteString.Lazy as BL import qualified Data.Map as M import Hercules.API.Id (Id, idText) import Hercules.Agent.Sensitive (Sensitive (Sensitive, reveal), revealContainer)-import Hercules.Agent.WorkerProcess (runWorker) import qualified Hercules.Agent.WorkerProcess as WorkerProcess-import Hercules.Agent.WorkerProtocol.Command.StartDaemon (StartDaemon (StartDaemon))-import qualified Hercules.Agent.WorkerProtocol.Command.StartDaemon as StartDaemon-import Hercules.Agent.WorkerProtocol.Event.DaemonStarted (DaemonStarted (DaemonStarted)) import Hercules.CNix (Derivation) import Hercules.CNix.Store (getDerivationArguments, getDerivationBuilder, getDerivationEnv) import Hercules.Effect.Container (BindMount (BindMount))@@ -25,11 +20,12 @@ import qualified Hercules.Formats.Secret as Formats.Secret import Hercules.Secrets (SecretContext, evalCondition, evalConditionTrace) import Katip (KatipContext, Severity (..), logLocM, logStr)+import Network.Socket (Family (AF_UNIX), SockAddr (SockAddrUnix), SocketType (Stream), bind, listen, socket, withFdSocket) import Protolude import System.FilePath-import UnliftIO (timeout)-import qualified UnliftIO+import System.Posix (dup, fdToHandle) import UnliftIO.Directory (createDirectory, createDirectoryIfMissing)+import UnliftIO.Process (withCreateProcess) import qualified UnliftIO.Process as Process  parseDrvSecretsMap :: Map ByteString ByteString -> Either Text (Map Text Text)@@ -206,32 +202,27 @@  withNixDaemonProxy :: [(Text, Text)] -> FilePath -> IO a -> IO a withNixDaemonProxy extraNixOptions socketPath wrappedAction = do-  workerExe <- WorkerProcess.getWorkerExe-  let opts = ["nix-daemon", show extraNixOptions]-      procSpec =-        (Process.proc workerExe opts)-          { -- Process.env = Just workerEnv,-            Process.close_fds = True-            -- Process.cwd = Just workDir-          }-  daemonCmdChan <- liftIO newChan-  daemonReadyVar <- liftIO newEmptyMVar-  let onDaemonEvent :: MonadIO m => DaemonStarted -> m ()-      onDaemonEvent DaemonStarted {} = liftIO (void $ tryPutMVar daemonReadyVar ())-  liftIO $ writeChan daemonCmdChan (Just $ Just $ StartDaemon {socketPath = socketPath})-  UnliftIO.withAsync (runWorker procSpec (\_ s -> liftIO (C8.hPutStrLn stderr ("hci proxy nix-daemon: " <> s))) daemonCmdChan onDaemonEvent) $ \daemonAsync -> do-    race (readMVar daemonReadyVar) (wait daemonAsync) >>= \case-      Left _ -> pass-      Right e -> throwIO $ FatalError $ "Hercules CI proxy nix-daemon exited before being told to; status " <> show e--    a <- wrappedAction+  -- Open the socket asap, so we don't have to wait for+  -- a readiness signal from the daemon, or poll, etc.+  sock <- socket AF_UNIX Stream 0+  bind sock (SockAddrUnix socketPath)+  listen sock 100 -    timeoutResult <- timeout (60 * 1000 * 1000) $ do-      writeChan daemonCmdChan (Just Nothing)-      writeChan daemonCmdChan Nothing-      void $ wait daemonAsync-    case timeoutResult of-      Nothing -> putErrText "warning: Hercules CI proxy nix-daemon did not shut down in time."-      _ -> pass+  -- (Ab)use stdin to transfer the socket while securely+  -- closing all other fds+  socketAsHandle <- withFdSocket sock \fd -> do+    fd' <- dup (fromIntegral fd)+    fdToHandle fd' -    pure a+  exe <- WorkerProcess.getDaemonExe+  let opts = extraNixOptions >>= \(k, v) -> ["--option", toS k, toS v]+      procSpec =+        (Process.proc exe opts)+          { -- close all other fds to be secure+            Process.close_fds = True,+            Process.std_in = Process.UseHandle socketAsHandle,+            Process.std_err = Process.Inherit,+            Process.std_out = Process.UseHandle stderr+          }+  withCreateProcess procSpec $ \_in _out _err _processHandle -> do+    wrappedAction