nova-nix: Windows-native Nix implementation in Haskell and C99

[ apache, distribution, library, nix, program, system ] [ Propose Tags ] [ Report a vulnerability ]

An implementation of Nix for Windows, written in Haskell with a C99 data layer that keeps evaluation data off the GHC heap. It also runs on macOS and Linux. It has its own parser, lazy evaluator, store, derivation builder and binary-cache substituter, and does not need an existing Nix installation.

On the 22 derivations its CI checks, over pinned pairs of Nix and nixpkgs from 2.24.9 with nixpkgs 24.11 to 2.35.4 with nixpkgs unstable, the derivation paths it computes match upstream. On Windows it builds packages from source through a stage-1 stdenv over a store-pinned MinGW-w64 toolchain. The project is experimental; see the README for what is not implemented yet.

Evaluation needs the C data layer, so library code must run it between Nix.Eval.Arena.arenaInit and Nix.Eval.Arena.arenaDestroy.

Built on nova-cache for NAR serialization, narinfo handling, and Ed25519-signed binary substitution.


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0, 0.1.0.1, 0.1.1.0, 0.1.2.0, 0.1.3.0, 0.1.4.0, 0.1.5.0, 0.1.6.0, 0.1.7.0, 0.1.7.1, 0.1.8.0, 0.1.9.0, 0.2.0.0, 0.3.0.0, 0.4.0.0, 0.5.0.0, 0.6.0.0, 0.8.0.0, 0.9.0.0
Change log CHANGELOG.md
Dependencies array (>=0.5 && <0.6), base (>=4.22 && <5), bytestring (>=0.11 && <0.13), containers (>=0.6 && <0.9), crypton (>=1.1 && <2), directory (>=1.3.8 && <1.4), file-io (>=0.1.1 && <0.2), filelock (>=0.1.1 && <0.2), filepath (>=1.4.100 && <1.6), http-client (>=0.7 && <0.8), http-client-tls (>=0.3 && <0.5), http-types (>=0.12 && <0.13), mtl (>=2.2 && <2.4), nova-cache (>=0.11.1.1 && <0.12), nova-nix, process (>=1.6 && <1.7), ram (>=0.20 && <1), random (>=1.2 && <1.4), regex-tdfa (>=1.3 && <1.4), sqlite-simple (>=0.4 && <0.5), tar (>=0.7.1 && <0.8), text (>=2.0 && <2.2), time (>=1.9 && <1.17), unix (>=2.8 && <2.9), Win32 (>=2.9 && <2.15), zstd (>=0.1 && <0.2) [details]
Tested with ghc ==9.14.1
License Apache-2.0
Copyright 2026 Novavero AI Inc.
Author Devon Tomlin
Maintainer devon.tomlin@novavero.ai
Uploaded by aoinoikaz at 2026-11-01T01:34:42Z
Category Nix, Distribution, System
Home page https://github.com/Novavero-AI/nova-nix
Bug tracker https://github.com/Novavero-AI/nova-nix/issues
Source repo head: git clone https://github.com/Novavero-AI/nova-nix -b main
Distributions
Executables nova-nix
Downloads 375 total (84 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for nova-nix-0.9.0.0

[back to package description]

nova-nix

CI Hackage License

nova-nix exists to make Nix work natively on Windows: the same language, the same /nix/store paths and the same binary caches as upstream Nix, without WSL or an existing Nix installation.

It is an implementation of Nix in Haskell with a C99 data layer, and it has its own parser, evaluator, store, builder and binary-cache substituter. It also builds and runs on macOS and Linux.

nova-nix is experimental. Read Limitations before relying on it.

Status

  • Evaluation matches upstream Nix on the cases CI checks. 22 derivations evaluate to the same store paths as upstream on three pinned pairings: Nix 2.24.9 with nixpkgs 24.11, Nix 2.34.9 with nixpkgs 26.05, and Nix 2.35.4 with nixpkgs unstable. They span the native, i686 and musl stdenv bootstraps, cross-compilation to aarch64 Linux and Windows, multi-output packages such as curl and systemd, sources fetched through git, GitHub, PyPI and the cargo and Go vendoring fetchers, a NixOS initrd and system, and fixtures for derivation inputs, builtins.toFile and builtins.fetchGit. 49 smaller evaluation cases produce the same output. CI runs this comparison on every change. A matching drvPath means the whole build-time closure behind it matches too. How much of the rest of nixpkgs evaluates has not been measured yet (#29).
  • Windows builds run natively. CI builds GNU Hello on a Windows runner through a stage-1 stdenv. Its toolchain is 17 MinGW-w64 packages and 22 MSYS2 packages, each fetched as a fixed-output derivation pinned by SHA-256 and unpacked into the store. sed, zlib and a small library-linking example also have recipes, but CI does not build them. Replacing these binary seeds with a toolchain built from source is tracked in #26.
  • Binary caches work in both directions. nova-nix push uploads a closure, and build --substituter downloads signed NARs instead of building. CI checks this two ways. On Linux, two fixtures are pushed to a local nova-cache server and substituted into a second empty store with signature verification on: a small tree whose two copies must serialize to the same NAR, and a 320 MiB tree that must substitute under a 128 MiB heap cap. On every push to main, a Windows job builds the Hello closure, runs Hello and publishes the closure to the public cache at cache.novavero.ai, and a second Windows job builds the same closure into an empty store with that cache as its only substituter, fails if any path is built locally, and runs the result.

Limitations

  • No flakes, no nix-shell or nix develop, and no daemon or multi-user mode.
  • Evaluation is slower than upstream's. CI's 22 drvPath cases take 10 to 13 times as long in total, and a full NixOS system takes 22 to 30 times as long.
  • build --substituter works through a package's build closure from the leaves up, so a package a cache holds still downloads its build-time dependencies (#307).
  • Garbage collection has no runtime roots: nova-nix store gc keeps what build --out-link registered and what the operator roots under the store's .nova-nix/gcroots, not what a running program has open. keep-derivations and keep-outputs are not implemented, so an unrooted .drv is collected. build creates no result link unless --out-link is given, where nix-build defaults to ./result.
  • --restrict-eval and --pure-eval exist (and restrict-eval, pure-eval and allowed-uris in nix.conf), gating reads, the environment and fetches the way upstream's modes do. The closure of a search path root that is a store path is not allowed along with it, and the bundled <nix/*> directory stays listed in builtins.nixPath under --pure-eval, where upstream shows an empty list.
  • The Windows store is C:\nix\store, where upstream's Windows default is %PROGRAMDATA%\nix\store, so the two do not share store paths on Windows (#309).
  • On Windows a build's process tree runs in a job object, so stopping a build stops everything it started. No platform has filesystem or network isolation yet (#25).
  • nova-nix itself needs neither WSL nor Cygwin, but the current Windows stdenv runs its build scripts with MSYS2's bash.
  • builtins.fetchurl runs curl, builtins.fetchTarball runs sh, curl and tar, and builtins.fetchGit runs git, each from PATH.
  • Store paths that contain symlinks need Windows Developer Mode or an elevated shell. nova-nix creates native symlinks and fails rather than falling back to a copy.

Install

Each release has an archive per platform:

Platform Archive
Linux x86_64 nova-nix-linux-x64.tar.gz
macOS arm64 nova-nix-macos-arm64.tar.gz
Windows x86_64 nova-nix-windows-x64.zip

Each archive unpacks to a single directory holding bin/, share/nova-nix/ and pkgs/. share/nova-nix/ provides the <nix/*> search path, so the binary works wherever the directory is unpacked, and pkgs/ holds the package recipes. Check the download against the SHA256SUMS file attached to the same release.

In a GitHub Actions workflow:

- uses: Novavero-AI/install-nova-nix@v1
- run: nova-nix eval --expr '1 + 2'

Usage

$ nova-nix eval --expr '1 + 2'
3
$ nova-nix eval --strict --expr 'builtins.map (x: x * x) [ 1 2 3 4 5 ]'
[ 1 4 9 16 25 ]
$ nova-nix eval FILE.nix                          # evaluate a file
$ nova-nix build FILE.nix -A ATTR                 # build an attribute of it
$ nova-nix build FILE.nix --substituter URL --trusted-key KEY  # try a cache first
$ nova-nix build FILE.nix --out-link result       # link the result and root it
$ nova-nix store gc                               # delete everything unrooted
$ nova-nix push --cache URL --key-file FILE --all  # upload every path except derivations
$ nova-nix --help

Evaluating a package from nixpkgs, on the 24.11 revision CI pins:

$ NIX_PATH=nixpkgs=/path/to/nixpkgs nova-nix eval --expr \
    '(import <nixpkgs> { system = "x86_64-linux"; config = {}; overlays = []; }).hello.drvPath'
"/nix/store/gciipqhqkdlqqn803zd4a389v86ran45-hello-2.12.1.drv"

Building GNU Hello on Windows, from the unpacked release directory:

> bin\nova-nix build pkgs\windows\hello.nix
...
C:\nix\store\<hash>-hello
> C:\nix\store\<hash>-hello\bin\hello.exe
Hello, world!

The first build fetches 40 pinned archives (the 39 toolchain packages and the Hello source), then builds the MSYS2 seed, the MinGW-w64 seed and Hello. The public cache holds this closure as CI last built it from main, so a build that names the cache downloads what it holds and builds only what it lacks:

> bin\nova-nix build pkgs\windows\hello.nix --substituter https://cache.novavero.ai --trusted-key cache.novavero.ai-1:9gQ7tLWMM+2tdC9H5sKMJltDIPfD7X2GWlZe8Aa8hHQ=

How it works

  • Parser (Nix.Parser): a hand-written recursive-descent parser for the Nix language.
  • Evaluator (Nix.Eval): the AST compiles to a small bytecode that a lazy evaluator runs, memoizing thunks and detecting infinite recursion. The evaluator is generic over MonadEval, with a pure instance for tests and an IO instance for real evaluation. derivation is a lazy wrapper over the strict derivationStrict primop, as in upstream's src/libexpr/primops/derivation.nix, so referring to a package does not force its build closure.
  • Data layer (cbits/): attribute sets, lists, thunks, environments, symbols, context strings, lambdas and bytecode are C99 structures outside the GHC heap, freed together when an evaluation session ends. Haskell calls into C, and C never calls back.
  • Store (Nix.Store): a store at /nix/store, or C:\nix\store on Windows, with SQLite metadata, reference scanning and per-path locks.
  • Builder (Nix.Builder): orders derivations by their dependencies, substitutes each one a cache holds, and runs the rest in a scrubbed environment.
  • Substituter (Nix.Substituter): the HTTP binary-cache protocol, with Ed25519 signature checks and xz, zstd and bzip2 decompression, built on nova-cache.

On Windows, derivations keep the canonical /nix/store spelling that hashes are computed over, and it is mapped to the real store directory only when a builder is started. NTFS has no executable bit, so nova-nix keeps it in an alternate data stream. A store path therefore serializes to the same NAR, with the same hash, as it does on other platforms.

Library

{-# LANGUAGE OverloadedStrings #-}

import Control.Exception (bracket_)
import Nix.Builtins (builtinEnv)
import Nix.Eval (eval, runPureEval)
import Nix.Eval.Arena (arenaDestroy, arenaInit)
import Nix.Eval.Policy (unrestrictedPolicy)
import Nix.Parser (parseNix)

main :: IO ()
main = bracket_ arenaInit arenaDestroy $
  -- The first argument is what relative path literals resolve against.
  case parseNix "/tmp" "<expr>" "let x = 5; in x * 2 + 1" of
    Left err -> print err
    Right expr -> print (runPureEval (eval (builtinEnv unrestrictedPolicy 0 []) expr))

This prints Right (VInt 11). Evaluation needs the C data layer, so it must run between arenaInit and arenaDestroy.

Building from source

Tested with GHC 9.14.1. CI uses the latest cabal-install release. You only need this to work on nova-nix itself, not to use a release.

git clone https://github.com/Novavero-AI/nova-nix.git
cd nova-nix
cabal update
cabal build
cabal test

Contributing

See CONTRIBUTING.md. Planned work is on the Nova roadmap project. Please report security issues privately through GitHub's vulnerability reporting rather than in a public issue.

License

Apache-2.0. See LICENSE and NOTICE.