nova-cache: Pure Nix binary cache protocol library

[ distribution, library, mit, nix ] [ Propose Tags ] [ Report a vulnerability ]

A focused, minimal, pure-first library implementing the full Nix binary cache protocol — nix-base32, NAR serialization, narinfo parsing, Ed25519 signing, store path handling — with an optional WAI server.


[Skip to Readme]

Flags

Manual Flags

NameDescriptionDefault
server

Build the cache server executable (pulls in warp/wai)

Disabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0
Change log CHANGELOG.md
Dependencies base (>=4.16 && <5), base64-bytestring (>=1.2 && <1.3), bytestring (>=0.11 && <0.13), containers (>=0.6 && <0.8), crypton (>=1.0 && <2), directory (>=1.3 && <1.4), filepath (>=1.4 && <1.6), http-types (>=0.12 && <0.13), lzma (>=0.0.1 && <0.1), memory (>=0.18 && <1), nova-cache, text (>=2.0 && <2.2), unix (>=2.7 && <2.9), vector (>=0.12 && <0.14), wai (>=3.2 && <3.3), warp (>=3.3 && <3.5) [details]
Tested with ghc ==9.6.7
License MIT
Author Devon Tomlin
Maintainer devon.tomlin@novavero.ai
Uploaded by aoinoikaz at 2026-02-22T00:26:34Z
Category Nix, Distribution
Home page https://github.com/Novavero-AI/nova-cache
Bug tracker https://github.com/Novavero-AI/nova-cache/issues
Source repo head: git clone https://github.com/Novavero-AI/nova-cache -b main
Distributions
Executables nova-cache-server
Downloads 0 total (0 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2026-02-22 [all 1 reports]

Readme for nova-cache-0.1.0.0

[back to package description]

nova-cache

Nix Binary Cache Protocol for Haskell

Pure-first implementation of the complete Nix binary cache protocol — base32, NAR, narinfo, Ed25519 signing, store paths — with an optional WAI server.

Quick Start · Modules · Server · API Reference

CI Hackage Haskell License


What is nova-cache?

A focused, minimal library implementing the full Nix binary cache protocol:

  • Base32 — Nix-specific encoding with the custom 0123456789abcdfghijklmnpqrsvwxyz alphabet, O(n) ST-based decode
  • Hash — SHA-256 hashing with sha256:<nix-base32> formatting, composition pipeline over crypton
  • StorePath — Store path parsing, validation, and hash extraction with enforced invariants via newtypes
  • NAR — Binary serialization and deserialization of the Nix ARchive format using Builder monoid composition
  • NarInfo — Parse and render the key-value narinfo text format
  • Signing — Ed25519 fingerprint signing and verification for binary cache trust
  • Compression — xz compress/decompress for NAR transport
  • Store — Filesystem storage backend for narinfo and NAR files
  • Server — Optional WAI/Warp HTTP server implementing the cache protocol (behind server cabal flag)

Every module is pure by default. IO lives at the boundaries only.


Quick Start

Add to your .cabal file:

build-depends: nova-cache

Hash a Store Path

import NovaCache.Hash (hashBytes, formatNixHash)
import qualified Data.ByteString as BS

main :: IO ()
main = do
  contents <- BS.readFile "/nix/store/abc123-hello/bin/hello"
  let nixHash = hashBytes contents
  putStrLn (show (formatNixHash nixHash))
  -- "sha256:0m6g5r7c..."

Parse a NarInfo

import NovaCache.NarInfo (parseNarInfo, niStorePath, niNarHash)

main :: IO ()
main = do
  let raw = "StorePath: /nix/store/abc...-hello\n\
            \URL: nar/abc....nar.xz\n\
            \Compression: xz\n\
            \FileHash: sha256:...\n\
            \FileSize: 1234\n\
            \NarHash: sha256:...\n\
            \NarSize: 5678\n\
            \References: \n"
  case parseNarInfo raw of
    Left err -> putStrLn ("Parse error: " ++ err)
    Right ni -> do
      putStrLn ("Store path: " <> show (niStorePath ni))
      putStrLn ("NAR hash:   " <> show (niNarHash ni))

Sign and Verify

import NovaCache.Signing (parseSecretKey, parsePublicKey, sign, verify)

main :: IO ()
main = do
  let Right sk = parseSecretKey "mykey:base64secretkey..."
      Right pk = parsePublicKey "mykey:base64pubkey..."
  case sign sk narinfo of
    Left err  -> putStrLn ("Sign error: " ++ err)
    Right sig -> putStrLn ("Verified: " <> show (verify pk narinfo sig))

Modules

NovaCache.Base32

Nix uses a non-standard base32 alphabet that omits e, o, u, t and encodes in reverse byte order. The encoder extracts 5-bit groups from descending positions; the decoder uses an O(n) ST-based bit scatter with mutable vectors.

import NovaCache.Base32 (encode, decode)

encode "\xff"       -- "8z"
decode "0z"         -- Right "\x1e"
decode (encode bs)  -- Right bs  (roundtrip)

NovaCache.Hash

SHA-256 via crypton, formatted as sha256:<nix-base32>:

import NovaCache.Hash (hashBytes, hashFile, formatNixHash, parseNixHash)

formatNixHash (hashBytes "hello")
-- "sha256:0m6g5r7c..."

nixHash <- hashFile "/nix/store/abc123-hello"

NovaCache.StorePath

Newtypes enforce invariants — store path hashes are always 32 characters, names are validated against the Nix character set:

import NovaCache.StorePath

let Right sp = parseStorePath defaultStoreDir "/nix/store/abc123...-hello-1.0"
storePathHashString sp  -- "abc123..."
storePathBaseName sp    -- "abc123...-hello-1.0"
renderStorePath defaultStoreDir sp  -- "/nix/store/abc123...-hello-1.0"

NovaCache.NAR

The NAR tree ADT with Builder-based serialization:

import NovaCache.NAR

let entry = NarRegular False "file contents"
let bytes = serialise entry
deserialise bytes  -- Right entry (roundtrip)

-- Hash a NAR directly (serialise + SHA-256)
narHash entry  -- NixHash

-- Build from filesystem
tree <- serialiseFromPath "/nix/store/abc123-hello"

NovaCache.Signing

Ed25519 signing and verification of narinfo fingerprints. Keys are name:base64 pairs:

import NovaCache.Signing

-- Fingerprint format: 1;<StorePath>;<NarHash>;<NarSize>;<refs>
fingerprint narinfo
-- "1;/nix/store/abc...;sha256:...;5678;"

Server

Enable the WAI server with the server cabal flag:

cabal build --flag server
cabal run nova-cache-server -- --port 5000 --store ./nix-cache

Or via environment variables:

PORT=5000 NIX_CACHE_DIR=./nix-cache nova-cache-server

Endpoints

Method Path Description
GET /nix-cache-info Cache metadata (StoreDir, WantMassQuery, Priority)
GET /<hash>.narinfo Fetch narinfo by store path hash
GET /nar/<file> Fetch compressed NAR file
PUT /<hash>.narinfo Upload narinfo
PUT /nar/<file> Upload NAR file

Use as a Nix Substituter

# Push to your cache
nix copy --to http://cache.example.com /nix/store/abc123-hello

# Pull from your cache
nix build --substituters http://cache.example.com --trusted-public-keys "mykey:base64..."

Architecture

                    Pure Core (no IO)
  ┌──────────────────────────────────────────┐
  │                                          │
  │  Base32 ──→ Hash ──→ StorePath           │
  │                         │                │
  │                      NarInfo ──→ Signing │
  │                         │                │
  │                        NAR               │
  │                                          │
  └──────────────────────────────────────────┘
                       │
              IO Boundary (thin)
  ┌──────────────────────────────────────────┐
  │  Compression    Store    Server (flag)   │
  └──────────────────────────────────────────┘
  • 8 modules, 6 pure + 2 at the IO boundary
  • 48 tests, hand-rolled harness, no framework dependencies
  • Zero partial functions — total by construction
  • Strict by default — bang patterns on all data fields

API Reference

Base32

encode :: ByteString -> Text
decode :: Text -> Either String ByteString

Hash

hashBytes     :: ByteString -> NixHash
hashFile      :: FilePath -> IO NixHash
formatNixHash :: NixHash -> Text
parseNixHash  :: Text -> Either String NixHash

StorePath

parseStorePath      :: StoreDir -> Text -> Either String StorePath
renderStorePath     :: StoreDir -> StorePath -> Text
storePathHashString :: StorePath -> Text
storePathBaseName   :: StorePath -> Text

NAR

serialise         :: NarEntry -> ByteString
deserialise       :: ByteString -> Either String NarEntry
narHash           :: NarEntry -> NixHash
serialiseFromPath :: FilePath -> IO NarEntry

NarInfo

parseNarInfo  :: Text -> Either String NarInfo
renderNarInfo :: NarInfo -> Text

Signing

parseSecretKey :: Text -> Either String SecretKey
parsePublicKey :: Text -> Either String PublicKey
fingerprint    :: NarInfo -> Text
sign           :: SecretKey -> NarInfo -> Either String Text
verify         :: PublicKey -> NarInfo -> Text -> Bool

Compression

compressXz   :: ByteString -> ByteString
decompressXz :: ByteString -> ByteString

Store

newFileStore  :: FilePath -> IO FileStore
readNarInfo   :: FileStore -> Text -> IO (Maybe ByteString)
writeNarInfo  :: FileStore -> Text -> ByteString -> IO ()
readNar       :: FileStore -> Text -> IO (Maybe ByteString)
writeNar      :: FileStore -> Text -> ByteString -> IO ()
getCacheInfo  :: FileStore -> (Text, Bool, Int)

Full Haddock documentation is available on Hackage.


Build & Test

cabal build                              # Build library
cabal test                               # Run all tests (48 tests, 8 groups)
cabal build --ghc-options="-Werror"      # Warnings as errors
cabal build --flag server                # Build with WAI server
cabal haddock                            # Generate docs

MIT License · Novavero AI