diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,10 @@
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
+## 0.5.0.0 - 2025-07-18
+
+- `init` now installs synchronous signal handlers for you. You may remove your call to `installDefaultSigINTHandler`.
+
 ## 0.4.0.0 - 2025-05-05
 
 - **REMOVE** `evalArgs`. This removes a dependency on the otherwise unused nix-cmd library. Use `valueFromExpressionString` and a library like `optparse-applicative` instead to create a nicer user experience.
diff --git a/hercules-ci-cnix-expr.cabal b/hercules-ci-cnix-expr.cabal
--- a/hercules-ci-cnix-expr.cabal
+++ b/hercules-ci-cnix-expr.cabal
@@ -1,7 +1,7 @@
 cabal-version: 2.4
 
 name:           hercules-ci-cnix-expr
-version:        0.4.0.0
+version:        0.5.0.0
 synopsis:       Bindings for the Nix evaluator
 category:       Nix, CI, Testing, DevOps
 homepage:       https://docs.hercules-ci.com
@@ -20,39 +20,15 @@
   type: git
   location: https://github.com/hercules-ci/hercules-ci-agent
 
-flag nix-2_8
-  description: Build for Nix >=2.8
-  default: True
 
--- Deprecated
-flag nix-2_4
-  description: Build for Nix >=2.4pre*
-  default: True
-
-flag nix-2_15
-  description: Build for Nix >=2.5pre*
-  default: False
-
-flag nix-2_24
-  description: Build for Nix >=2.24
-  default: False
-
-flag nix-2_25
-  description: Build for Nix >=2.25
-  default: False
-
 flag ide
   description: Whether to enable IDE workarounds. You shouldn't need this.
   default: False
 
 -- match the C++ language standard Nix is using
 common cxx-opts
-  if flag(nix-2_15)
-    cxx-options:
-      -std=c++2a
-  else
-    cxx-options:
-      -std=c++17
+  cxx-options:
+    -std=c++2a
 
   cxx-options:
     -Wall
@@ -65,16 +41,11 @@
   if impl(ghc >= 8.10)
     ghc-options:
       -optcxx-Wall
-    if flag(nix-2_15)
-      ghc-options:
-        -optcxx-std=c++2a
-    else
-      ghc-options:
-        -optcxx-std=c++17
+      -optcxx-std=c++2a
   else
     -- Remove soon
     ghc-options:
-      -optc-std=c++17
+      -optc-std=c++2a
       -optc-Wall
     if os(darwin)
       ghc-options: -pgmc=clang++
@@ -126,12 +97,11 @@
     extra-libraries:
         boost_context
   pkgconfig-depends:
-      nix-store >= 2.4
-    , nix-expr >= 2.4
-    , nix-main >= 2.4
-  if flag(nix-2_24)
-    pkgconfig-depends:
-      nix-flake
+      nix-store >= 2.28
+    , nix-expr >= 2.28
+    -- TODO: replace nix-main stack overflow detection?
+    , nix-main >= 2.28
+    , nix-flake >= 2.28
 
 
 test-suite hercules-ci-cnix-expr-unit-tests
@@ -170,6 +140,3 @@
       hspec-discover:hspec-discover == 2.*
   default-language: Haskell2010
 
-  -- Missing nix-fetchers.pc upstream in Nix 2.24
-  if flag(nix-2_24) && ! flag(nix-2_25)
-    extra-libraries: nixfetchers
diff --git a/include/hercules-ci-cnix/expr.hxx b/include/hercules-ci-cnix/expr.hxx
--- a/include/hercules-ci-cnix/expr.hxx
+++ b/include/hercules-ci-cnix/expr.hxx
@@ -1,10 +1,6 @@
 #pragma once
 
-#if NIX_IS_AT_LEAST(2,28,0)
 #include <nix/store/derivations.hh>
-#else
-#include "derivations.hh"
-#endif
 
 #include <hercules-ci-cnix/store.hxx>
 
diff --git a/src/Hercules/CNix/Expr.hs b/src/Hercules/CNix/Expr.hs
--- a/src/Hercules/CNix/Expr.hs
+++ b/src/Hercules/CNix/Expr.hs
@@ -87,6 +87,7 @@
 import Hercules.CNix.Expr.Typed
 import Hercules.CNix.Store
 import Hercules.CNix.Store.Context
+import qualified Hercules.CNix as CNix
 import qualified Language.C.Inline.Cpp as C
 import qualified Language.C.Inline.Cpp.Exception as C
 import Protolude hiding (evalState)
@@ -104,8 +105,6 @@
 
 C.include "<math.h>"
 
-#if NIX_IS_AT_LEAST(2, 28, 0)
-
 C.include "<nix/util/config-global.hh>"
 C.include "<nix/store/store-api.hh>"
 C.include "<nix/store/globals.hh>"
@@ -118,29 +117,6 @@
 C.include "<nix/fetchers/fetch-settings.hh>"
 C.include "<nix/main/shared.hh>"
 
-#else
-C.include "<nix/config.h>"
-C.include "<nix/shared.hh>"
-C.include "<nix/eval.hh>"
-C.include "<nix/eval-inline.hh>"
-C.include "<nix/store-api.hh>"
-C.include "<nix/get-drvs.hh>"
-C.include "<nix/derivations.hh>"
-C.include "<nix/globals.hh>"
-C.include "<nix/flake/flake.hh>"
-C.include "<nix/flake/flakeref.hh>"
-#  if NIX_IS_AT_LEAST(2,19,0)
-C.include "<nix/args/root.hh>"
-#  endif
-#  if NIX_IS_AT_LEAST(2,24,0)
-C.include "<nix/config-global.hh>"
-C.include "<nix/eval-gc.hh>"
-C.include "<nix/flake/settings.hh>"
-C.include "<nix/fetch-settings.hh>"
-C.include "<nix/eval-settings.hh>"
-#  endif
-#endif
-
 C.include "hercules-ci-cnix/expr.hxx"
 
 C.include "hercules-ci-cnix/string.hxx"
@@ -163,32 +139,18 @@
 C.verbatim "nix::EvalSettings evalSettings(readOnlyMode);"
 
 init :: IO ()
-init =
-  void
-    [C.throwBlock| void {
-      { auto _ = GlobalConfig::Register(&flakeSettings); }
-      { auto _ = GlobalConfig::Register(&fetchSettings); }
-      { auto _ = GlobalConfig::Register(&evalSettings); }
-      nix::initNix();
-      nix::initGC();
-#if NIX_IS_AT_LEAST(2,15,0)
-      globalConfig.set("extra-experimental-features", "flakes");
-#else
-#if NIX_IS_AT_LEAST(2,5,0)
-      std::set<nix::ExperimentalFeature> features(nix::settings.experimentalFeatures.get());
-      features.insert(nix::ExperimentalFeature::Flakes);
-#else
-      Strings features(nix::settings.experimentalFeatures.get());
-      features.push_back("flakes");
-#endif
-      nix::settings.experimentalFeatures.assign(features);
-#endif
-#if NIX_IS_AT_LEAST(2,28,0)
-      flakeSettings.configureEvalSettings(evalSettings);
-#elif NIX_IS_AT_LEAST(2,24,0)
-      nix::flake::initLib(flakeSettings);
-#endif
-    } |]
+init = do
+  [C.throwBlock| void {
+    { auto _ = GlobalConfig::Register(&flakeSettings); }
+    { auto _ = GlobalConfig::Register(&fetchSettings); }
+    { auto _ = GlobalConfig::Register(&evalSettings); }
+  }|]
+  CNix.init
+  [C.throwBlock| void {
+    nix::initGC();
+    globalConfig.set("extra-experimental-features", "flakes");
+    flakeSettings.configureEvalSettings(evalSettings);
+  }|]
 
 -- | Initialize the current (main) thread for stack overflow detection.
 initThread :: IO ()
@@ -246,7 +208,6 @@
 -}
 setExtraStackOverflowHandlerToSleep :: IO ()
 setExtraStackOverflowHandlerToSleep =
-#if NIX_IS_AT_LEAST(2,12,0)
   void
     [C.throwBlock| void {
       nix::stackOverflowHandler = [](siginfo_t *info, void *ctx) {
@@ -261,9 +222,6 @@
         _exit(1);
         };
     }|]
-#else
-  pass
-#endif
 
 setTalkative :: IO ()
 setTalkative =
@@ -303,22 +261,10 @@
 -- | (private) Make an EvalState and leak it.
 newEvalState :: MonadIO m => Store -> m (Ptr EvalState)
 newEvalState (Store store) = liftIO
-#if NIX_IS_AT_LEAST(2,24,0)
   [C.throwBlock| EvalState* {
     nix::LookupPath emptyLookupPath;
     return new EvalState(emptyLookupPath, *$(refStore* store), fetchSettings, evalSettings);
   } |]
-#elif NIX_IS_AT_LEAST(2,17,0)
-  [C.throwBlock| EvalState* {
-    nix::SearchPath searchPaths;
-    return new EvalState(searchPaths, *$(refStore* store));
-  } |]
-#else
-  [C.throwBlock| EvalState* {
-    Strings searchPaths;
-    return new EvalState(searchPaths, *$(refStore* store));
-  } |]
-#endif
 
 -- | (private) Don't leak it.
 deleteEvalState :: MonadIO m => (Ptr EvalState) -> m ()
@@ -343,13 +289,7 @@
   [C.throwBlock| void {
     std::string path = std::string($bs-ptr:path, $bs-len:path);
     EvalState &evalState = *$(EvalState *evalState);
-#if NIX_IS_AT_LEAST(2,20,0)
     evalState.allowPath(path);
-#else
-    if (evalState.allowedPaths) {
-      evalState.allowedPaths->insert(path);
-    }
-#endif
   }|]
 
 addInternalAllowedPaths :: Ptr EvalState -> IO ()
@@ -364,13 +304,7 @@
       EvalState & state = *$(EvalState *evalState);
       Value value;
       auto cstr = $(const char *filename');
-#if NIX_IS_AT_LEAST(2,19,0)
       SourcePath path {state.rootPath(CanonPath(cstr))};
-#elif NIX_IS_AT_LEAST(2,16,0)
-      SourcePath path = CanonPath(cstr);
-#else
-      std::string path = cstr;
-#endif
       state.evalFile(path, value);
       return new (NoGC) Value(value);
     }|]
@@ -391,11 +325,7 @@
     =<< [C.throwBlock| Value* {
           Value result;
           $(EvalState *evalState)->autoCallFunction(
-#if NIX_IS_AT_LEAST(2,24,0)
                   const_cast<Bindings &>(*$(Value *autoArgs)->attrs()),
-#else
-                  *$(Value *autoArgs)->attrs,
-#endif
                   *$(Value *fun),
                   result);
           return new (NoGC) Value (result);
@@ -424,11 +354,7 @@
     <$> [C.throwBlock| int {
           Value *v = $(Value *v);
           EvalState &evalState = *$(EvalState *evalState);
-#if NIX_IS_AT_LEAST(2,24,0)
           auto attrs = v->attrs();
-#else
-          auto attrs = v->attrs;
-#endif
           // Bindings::const_iterator iter; // 2.24
           auto iter = attrs->find(evalState.sRecurseForDerivations);
           if (iter == attrs->end()) {
@@ -438,13 +364,7 @@
             // case to return true when it is not a bool. That logic was added
             // because an empty attrset was found here, observed in
             // nixpkgs master 67e2de195a4aa0a50ffb1e1ba0b4fb531dca67dc
-#if NIX_IS_AT_LEAST(2,14,0)
             return evalState.forceBool(*iter->value, iter->pos, "while evaluating whether to traverse into an attribute set to find more derivations");
-#elif NIX_IS_AT_LEAST(2,9,0)
-            return evalState.forceBool(*iter->value, iter->pos);
-#else
-            return evalState.forceBool(*iter->value, *iter->pos);
-#endif
           }
         } |]
 
@@ -455,11 +375,7 @@
       Value &v = *$(Value *v);
       EvalState &evalState = *$(EvalState *evalState);
       Symbol k = evalState.symbols.create($bs-cstr:k);
-#if NIX_IS_AT_LEAST(2,24,0)
       auto attrs = v.attrs();
-#else
-      auto attrs = v.attrs;
-#endif
       // Bindings::const_iterator iter; // 2.24
       auto iter = attrs->find(k);
       if (iter == attrs->end()) {
@@ -476,25 +392,16 @@
 
 getAttrs :: Ptr EvalState -> Value NixAttrs -> IO (Map ByteString RawValue)
 getAttrs evalState (Value (RawValue v)) = do
-#if NIX_IS_AT_LEAST(2,24,0)
   begin <- [C.exp| const Attr *{ $(Value *v)->attrs()->begin() }|]
   end <- [C.exp| const Attr *{ $(Value *v)->attrs()->end() }|]
-#else
-  begin <- [C.exp| Attr *{ $(Value *v)->attrs->begin() }|]
-  end <- [C.exp| Attr *{ $(Value *v)->attrs->end() }|]
-#endif
   let gather :: Map ByteString RawValue -> Ptr Attr' -> IO (Map ByteString RawValue)
       gather acc i | i == end = pure acc
       gather acc i = do
-#if NIX_IS_AT_LEAST(2,9,0)
         name <- BS.unsafePackMallocCString =<< [C.block| const char *{
           EvalState &evalState = *$(EvalState *evalState);
           SymbolStr str = evalState.symbols[$(Attr *i)->name];
           return stringdup(static_cast<std::string>(str));
         }|]
-#else
-        name <- BS.unsafePackMallocCString =<< [C.exp| const char *{ stringdup(static_cast<std::string>($(Attr *i)->name)) } |]
-#endif
         value <- mkRawValue =<< [C.exp| Value *{ new (NoGC) Value(*$(Attr *i)->value) } |]
         let acc' = M.insert name value acc
         seq acc' pass
@@ -508,18 +415,9 @@
       EvalState &state = *$(EvalState *evalState);
       auto drvInfo = getDerivation(state, *$(Value *v), false);
       if (!drvInfo)
-#if NIX_IS_AT_LEAST(2,24,0)
         throw EvalError(state, "Not a valid derivation");
-#else
-        throw EvalError("Not a valid derivation");
-#endif
 
-#if NIX_IS_AT_LEAST(2,7,0)
       StorePath storePath = drvInfo->requireDrvPath();
-#else
-      std::string drvPath = drvInfo->queryDrvPath();
-      StorePath storePath = state.store->parseStorePath(drvPath);
-#endif
 
       // write it (?)
       auto drv = state.store->derivationFromPath(storePath);
@@ -573,13 +471,7 @@
     =<< [C.throwBlock| Value *{
       EvalState &evalState = *$(EvalState *evalState);
       std::string basePathStr = std::string($bs-ptr:basePath, $bs-len:basePath);
-#if NIX_IS_AT_LEAST(2,19,0)
       SourcePath basePath {evalState.rootPath(CanonPath(basePathStr))};
-#elif NIX_IS_AT_LEAST(2,16,0)
-      SourcePath basePath = CanonPath(basePathStr);
-#else
-      auto & basePath = basePathStr;
-#endif
       Expr *expr = evalState.parseExprFromString(std::string($bs-ptr:s, $bs-len:s), basePath);
       Value *r = new (NoGC) Value();
       evalState.eval(expr, *r);
@@ -607,22 +499,14 @@
     }|]
 
 mkPath :: Ptr EvalState -> ByteString -> IO (Value NixPath)
-#if NIX_IS_AT_LEAST(2,19,0)
 mkPath evalState path =
-#else
-mkPath _evalState path =
-#endif
   Value
     <$> ( mkRawValue
             =<< [C.throwBlock| Value *{
       Value *r = new (NoGC) Value();
       std::string s($bs-ptr:path, $bs-len:path);
-#if NIX_IS_AT_LEAST(2,19,0)
       EvalState & state = *$(EvalState *evalState);
       r->mkPath(state.rootPath(CanonPath(s)));
-#else
-      r->mkPath(stringdup(s));
-#endif
       return r;
   }|]
         )
@@ -634,27 +518,19 @@
     Value *r = new (NoGC) Value();
     std::string flakeRefStr($bs-ptr:flakeRef, $bs-len:flakeRef);
     auto flakeRef = nix::parseFlakeRef(
-#if NIX_IS_AT_LEAST(2,24,0)
       fetchSettings,
-#endif
       flakeRefStr,
       {},
       true);
     nix::flake::callFlake(evalState,
       nix::flake::lockFlake(
-#if NIX_IS_AT_LEAST(2,24,0)
         flakeSettings,
-#endif
         evalState,
         flakeRef,
         nix::flake::LockFlags {
           .updateLockFile = false,
           .useRegistries = false,
-#if NIX_IS_AT_LEAST(2,13,0)
           .allowUnlocked = false,
-#else
-          .allowMutable = false,
-#endif
         }),
       *r);
     return r;
@@ -670,27 +546,19 @@
     Value *r = new (NoGC) Value();
     std::string path($bs-ptr:absPath, $bs-len:absPath);
     auto flakeRef = nix::parseFlakeRef(
-#if NIX_IS_AT_LEAST(2,24,0)
       fetchSettings,
-#endif
       path,
       {},
       true);
     nix::flake::callFlake(evalState,
       nix::flake::lockFlake(
-#if NIX_IS_AT_LEAST(2,24,0)
         flakeSettings,
-#endif
         evalState,
         flakeRef,
         nix::flake::LockFlags {
           .updateLockFile = false,
           .useRegistries = false,
-#if NIX_IS_AT_LEAST(2,13,0)
           .allowUnlocked = false,
-#else
-          .allowMutable = false,
-#endif
         }),
       *r);
     return r;
@@ -716,25 +584,17 @@
     attrs.emplace("rev", rev);
 
     auto flakeRef = nix::FlakeRef::fromAttrs(
-#if NIX_IS_AT_LEAST(2,24,0)
       fetchSettings,
-#endif
       attrs);
     nix::flake::callFlake(evalState,
       nix::flake::lockFlake(
-#if NIX_IS_AT_LEAST(2,24,0)
         flakeSettings,
-#endif
         evalState,
         flakeRef,
         nix::flake::LockFlags {
           .updateLockFile = false,
           .useRegistries = false,
-#if NIX_IS_AT_LEAST(2,13,0)
           .allowUnlocked = false,
-#else
-          .allowMutable = false,
-#endif
         }),
       *r);
     return r;
@@ -882,7 +742,6 @@
 
 instance ToRawValue a => ToRawValue (Map ByteString a)
 
-#if NIX_IS_AT_LEAST(2,6,0)
 withBindingsBuilder :: Integral n => Ptr EvalState -> n -> (Ptr BindingsBuilder' -> IO ()) -> IO (Value NixAttrs)
 withBindingsBuilder evalState n f = do
   withBindingsBuilder' evalState n \bb -> do
@@ -905,12 +764,10 @@
         return new BindingsBuilder(evalState, evalState.allocBindings($(int l)));
       }|]
       \bb -> [C.block| void { delete $(BindingsBuilder *bb); }|]
-#endif
 
 instance ToRawValue a => ToValue (Map ByteString a) where
   type NixTypeFor (Map ByteString a) = NixAttrs
 
-#if NIX_IS_AT_LEAST(2,6,0)
   toValue evalState attrs = withBindingsBuilder evalState (length attrs) \bb -> do
     attrs & traverseWithKey_ \k a -> do
       RawValue aRaw <- toRawValue evalState a
@@ -920,30 +777,6 @@
           Value &a = *$(Value *aRaw);
           $(BindingsBuilder *bb)->alloc(evalState.symbols.create(k)) = a;
         }|]
-#else
-  toValue evalState attrs = do
-    let l :: C.CInt
-        l = fromIntegral (length attrs)
-    v <-
-      [C.block| Value* {
-          EvalState &evalState = *$(EvalState *evalState);
-          Value *v = new (NoGC) Value();
-          evalState.mkAttrs(*v, $(int l));
-          return v;
-        }|]
-    attrs & traverseWithKey_ \k a -> do
-      RawValue aRaw <- toRawValue evalState a
-      [C.block| void {
-          EvalState &evalState = *$(EvalState *evalState);
-          std::string k($bs-ptr:k, $bs-len:k);
-          Value &a = *$(Value *aRaw);
-          *evalState.allocAttr(*$(Value *v), evalState.symbols.create(k)) = a;
-        }|]
-    [C.block| void {
-        $(Value *v)->attrs->sort();
-      }|]
-    Value <$> mkRawValue v
-#endif
 
 instance ToRawValue a => ToRawValue (Map Text a)
 
@@ -984,7 +817,6 @@
 instance ToRawValue a => ToValue (H.HashMap Text a) where
   type NixTypeFor (H.HashMap Text a) = NixAttrs
 
-#if NIX_IS_AT_LEAST(2,6,0)
   toValue evalState attrs = withBindingsBuilder evalState (length attrs) \bb -> do
     attrs & hmTraverseWithKey_ \k' a -> do
       RawValue aRaw <- toRawValue evalState a
@@ -995,38 +827,12 @@
           Value &a = *$(Value *aRaw);
           $(BindingsBuilder *bb)->alloc(evalState.symbols.create(k)) = a;
         }|]
-#else
-  toValue evalState attrs = do
-    let l :: C.CInt
-        l = fromIntegral (length attrs)
-    v <-
-      [C.block| Value* {
-          EvalState &evalState = *$(EvalState *evalState);
-          Value *v = new (NoGC) Value();
-          evalState.mkAttrs(*v, $(int l));
-          return v;
-        }|]
-    attrs & hmTraverseWithKey_ \k' a -> do
-      RawValue aRaw <- toRawValue evalState a
-      let k = encodeUtf8 k'
-      [C.block| void {
-          EvalState &evalState = *$(EvalState *evalState);
-          std::string k($bs-ptr:k, $bs-len:k);
-          Value &a = *$(Value *aRaw);
-          *evalState.allocAttr(*$(Value *v), evalState.symbols.create(k)) = a;
-        }|]
-    [C.block| void {
-        $(Value *v)->attrs->sort();
-      }|]
-    Value <$> mkRawValue v
-#endif
 
 instance ToRawValue a => ToRawValue (Vector a)
 
 instance ToRawValue a => ToValue (Vector a) where
   type NixTypeFor (Vector a) = NixList
   toValue evalState vec =
-#if NIX_IS_AT_LEAST(2,24,0)
     coerce <$> do
       let l :: C.CInt
           l = fromIntegral (length vec)
@@ -1049,26 +855,6 @@
           return v;
         }|]
       Value <$> mkRawValue v
-#else
-    coerce <$> do
-      let l :: C.CInt
-          l = fromIntegral (length vec)
-      v <-
-        [C.block| Value* {
-          EvalState &evalState = *$(EvalState *evalState);
-          Value *v = new (NoGC) Value();
-          evalState.mkList(*v, $(int l));
-          return v;
-        }|]
-      vec & V.imapM_ \i a -> do
-        RawValue aRaw <- toRawValue evalState a
-        let ix = fromIntegral i
-        [C.block| void {
-          Value &v = *$(Value *v);
-          v.listElems()[$(int ix)] = $(Value *aRaw);
-        }|]
-      Value <$> mkRawValue v
-#endif
 
 instance ToRawValue a => ToRawValue [a]
 
diff --git a/src/Hercules/CNix/Expr/Context.hs b/src/Hercules/CNix/Expr/Context.hs
--- a/src/Hercules/CNix/Expr/Context.hs
+++ b/src/Hercules/CNix/Expr/Context.hs
@@ -10,9 +10,7 @@
     Value',
     Attr',
     BindingsBuilder',
-#if NIX_IS_AT_LEAST(2,24,0)
     ListBuilder',
-#endif
     module Hercules.CNix.Store.Context,
     (=:),
   )
@@ -33,9 +31,7 @@
 
 data BindingsBuilder'
 
-#if NIX_IS_AT_LEAST(2,24,0)
 data ListBuilder'
-#endif
 
 context :: C.Context
 context =
@@ -59,8 +55,6 @@
           =: [t|Attr'|]
           <> C.TypeName "BindingsBuilder"
           =: [t|BindingsBuilder'|]
-#if NIX_IS_AT_LEAST(2,24,0)
           <> C.TypeName "ListBuilder"
           =: [t|ListBuilder'|]
-#endif
     }
diff --git a/src/Hercules/CNix/Expr/Raw.hs b/src/Hercules/CNix/Expr/Raw.hs
--- a/src/Hercules/CNix/Expr/Raw.hs
+++ b/src/Hercules/CNix/Expr/Raw.hs
@@ -20,16 +20,8 @@
 
 C.context context
 
-#if NIX_IS_AT_LEAST(2, 28, 0)
-
 C.include "<nix/expr/eval.hh>"
 C.include "<nix/expr/eval-inline.hh>"
-
-#else
-C.include "<nix/config.h>"
-C.include "<nix/eval.hh>"
-C.include "<nix/eval-inline.hh>"
-#endif
 
 C.include "<hercules-ci-cnix/expr.hxx>"
 C.include "<gc/gc.h>"
diff --git a/src/Hercules/CNix/Expr/Typed.hs b/src/Hercules/CNix/Expr/Typed.hs
--- a/src/Hercules/CNix/Expr/Typed.hs
+++ b/src/Hercules/CNix/Expr/Typed.hs
@@ -48,23 +48,9 @@
 
 C.include "<math.h>"
 
-#if NIX_IS_AT_LEAST(2, 28, 0)
-
 C.include "<nix/expr/eval.hh>"
 C.include "<nix/expr/eval-inline.hh>"
 
-#else
-C.include "<nix/config.h>"
-C.include "<nix/shared.hh>"
-C.include "<nix/eval.hh>"
-C.include "<nix/eval-inline.hh>"
-C.include "<nix/store-api.hh>"
--- C.include "<nix/common-eval-args.hh>"
-C.include "<nix/get-drvs.hh>"
-C.include "<nix/derivations.hh>"
-C.include "<nix/globals.hh>"
-#endif
-
 C.include "hercules-ci-cnix/expr.hxx"
 
 C.include "<gc/gc.h>"
@@ -152,42 +138,24 @@
 getBool :: Value Bool -> IO Bool
 getBool (Value (RawValue v)) =
   (0 /=) <$>
-#if NIX_IS_AT_LEAST(2,24,0)
     [C.exp| int { $(Value *v)->boolean() ? 1 : 0 }|]
-#else
-    [C.exp| int { $(Value *v)->boolean ? 1 : 0 }|]
-#endif
 
 getInt :: Value NixInt -> IO Int64
 getInt (Value (RawValue v)) =
-#if NIX_IS_AT_LEAST(2,27,0)
   [C.exp| int64_t { $(Value *v)->integer().value }|]
-#elif NIX_IS_AT_LEAST(2,24,0)
-  [C.exp| int64_t { $(Value *v)->integer() }|]
-#else
-  [C.exp| int64_t { $(Value *v)->integer }|]
-#endif
 
 -- NOT coerceToString
 getStringIgnoreContext :: Value NixString -> IO ByteString
 getStringIgnoreContext (Value (RawValue v)) =
   BS.unsafePackMallocCString
     =<< [C.exp| const char *{
-#if NIX_IS_AT_LEAST(2,19,0)
     strdup($(Value *v)->c_str())
-#else
-    strdup($(Value *v)->string.s)
-#endif
   }|]
 
 hasContext :: Value NixString -> IO Bool
 hasContext (Value (RawValue v)) =
   (0 /=) <$>
-#if NIX_IS_AT_LEAST(2,24,0)
     [C.exp| int { $(Value *v)->context() ? 1 : 0 }|]
-#else
-    [C.exp| int { $(Value *v)->string.context ? 1 : 0 }|]
-#endif
 
 class CheckType a where
   checkType :: Ptr EvalState -> RawValue -> IO (Maybe (Value a))
