packages feed

nova-cache 0.2.0.0 → 0.2.1.0

raw patch · 6 files changed

+67/−10 lines, 6 files

Files

CHANGELOG.md view
@@ -1,5 +1,17 @@ # Changelog +## 0.2.1.0 — 2026-02-22++- Server: `validateNarInfo` wired into PUT handler — rejects malformed uploads+  with 400 Bad Request and collected validation errors+- Server: `Cache-Control: public, max-age=31536000, immutable` on narinfo and+  NAR GET responses for CDN edge caching+- Store: default priority changed from 30 to 50 (community cache fallback+  behind cache.nixos.org at 40)+- Seed action: fix round-trip validation to account for server-side signing;+  now verifies StorePath field, signature presence, and NAR fetchability+- Public binary cache documented with key and nix.conf instructions+ ## 0.2.0.0 — 2026-02-22  - New module: `NovaCache.Validate` — pure protocol validation layer
README.md view
@@ -232,7 +232,27 @@ | `PUT` | `/<hash>.narinfo` | Upload narinfo | | `PUT` | `/nar/<file>` | Upload NAR file | -### Use as a Nix Substituter+### Public Binary Cache++A free, public binary cache is available at `cache.novavero.ai`. Add it to+your Nix configuration:++```nix+# nix.conf or /etc/nix/nix.conf+extra-substituters = https://cache.novavero.ai+extra-trusted-public-keys = cache.novavero.ai-1:2yJK0UZWlDDTpThzEdqfGWaj+j3ljOCGoA50Ims47dM=+```++Or use it directly:++```bash+nix build --substituters "https://cache.nixos.org https://cache.novavero.ai" \+          --trusted-public-keys "cache.nixos.org-1:DLD/YGKmo6OceLp6RsiGCbi5FwMExRzJcoJKanMPe/Q= cache.novavero.ai-1:2yJK0UZWlDDTpThzEdqfGWaj+j3ljOCGoA50Ims47dM="+```++### Self-Hosted Cache++Run your own instance:  ```bash # Push to your cache
exe/Main.hs view
@@ -12,6 +12,7 @@ import NovaCache.NarInfo (NarInfo (..), parseNarInfo, renderNarInfo) import NovaCache.Signing (SecretKey, parseSecretKey, sign) import NovaCache.Store (FileStore, getCacheInfo, newFileStore, readNar, readNarInfo, writeNar, writeNarInfo)+import NovaCache.Validate (validateNarInfo) import System.Environment (getArgs, lookupEnv) import Text.Read (readMaybe) @@ -119,14 +120,21 @@         respond (responseLBS HTTP.status200 octetHeaders (BL.fromStrict content))       Nothing ->         respond (responseLBS HTTP.status404 textHeaders "not found")-  -- PUT /<hash>.narinfo (auth required)+  -- PUT /<hash>.narinfo (auth required, validated)   ("PUT", [hashNarinfo])     | Just hashKey <- T.stripSuffix ".narinfo" hashNarinfo ->         requireAuth cfg req respond $ do           body <- BL.toStrict <$> strictRequestBody req-          let signed = signNarInfo (cfgSigningKey cfg) body-          writeNarInfo (cfgStore cfg) hashKey signed-          respond (responseLBS HTTP.status200 textHeaders "ok")+          case parseNarInfo (TE.decodeUtf8 body) of+            Left err ->+              respond (badRequest (T.pack err))+            Right ni -> case validateNarInfo ni of+              Left errs ->+                respond (badRequest (T.unlines (map (T.pack . show) errs)))+              Right _ -> do+                let signed = signNarInfo (cfgSigningKey cfg) body+                writeNarInfo (cfgStore cfg) hashKey signed+                respond (responseLBS HTTP.status200 textHeaders "ok")   -- PUT /nar/<file> (auth required)   ("PUT", ["nar", fileName]) ->     requireAuth cfg req respond $ do@@ -189,14 +197,29 @@ boolText True = "1" boolText False = "0" +-- | 400 Bad Request with a text error message.+badRequest :: Text -> Response+badRequest msg = responseLBS HTTP.status400 textHeaders (BL.fromStrict (TE.encodeUtf8 msg))+ -- | Content-Type: text/plain headers. textHeaders :: HTTP.ResponseHeaders textHeaders = [(HTTP.hContentType, "text/plain")]  -- | Content-Type: application/x-nix-narinfo headers.+-- Narinfo files are content-addressed (keyed by store path hash) and+-- immutable once written, so they are safe to cache indefinitely at the+-- CDN edge. narInfoHeaders :: HTTP.ResponseHeaders-narInfoHeaders = [(HTTP.hContentType, "text/x-nix-narinfo")]+narInfoHeaders =+  [ (HTTP.hContentType, "text/x-nix-narinfo"),+    (HTTP.hCacheControl, "public, max-age=31536000, immutable")+  ]  -- | Content-Type: application/octet-stream headers.+-- NAR files are content-addressed (keyed by content hash) and immutable+-- once written, so they are safe to cache indefinitely at the CDN edge. octetHeaders :: HTTP.ResponseHeaders-octetHeaders = [(HTTP.hContentType, "application/octet-stream")]+octetHeaders =+  [ (HTTP.hContentType, "application/octet-stream"),+    (HTTP.hCacheControl, "public, max-age=31536000, immutable")+  ]
nova-cache.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               nova-cache-version:            0.2.0.0+version:            0.2.1.0 synopsis:           Pure Nix binary cache protocol library description:   A focused, minimal, pure-first library implementing the full Nix binary
src/NovaCache/Store.hs view
@@ -53,8 +53,10 @@ narSubdir = "nar"  -- | Default cache priority (lower = preferred by Nix).+-- Community caches should use a higher number than cache.nixos.org (40)+-- so the official cache is preferred and we serve as a fallback. defaultPriority :: Int-defaultPriority = 30+defaultPriority = 50  -- | Default Nix store directory. defaultStoreDir :: Text
test/Main.hs view
@@ -553,7 +553,7 @@         removeDirectoryRecursive tmpDir         ok1 <- assertEqual "storeDir" "/nix/store" storeDir         ok2 <- assertTrue "wantMassQuery" wantMass-        ok3 <- assertEqual "priority" 30 priority+        ok3 <- assertEqual "priority" 50 priority         pure (ok1 && ok2 && ok3),       test "sanitizePath rejects traversal" $         assertEqual "dotdot" Nothing (Store.sanitizePath ".."),