packages feed

nova-cache 0.4.2.0 → 0.4.2.1

raw patch · 10 files changed

+290/−109 lines, 10 filesdep ~containers

Dependency ranges changed: containers

Files

CHANGELOG.md view
@@ -1,12 +1,19 @@ # Changelog -## 0.4.2.0 — 2026-06-10+## 0.4.2.1 - 2026-06-12 -- **Signing: public-key derivation and rendering** — new `toPublicKey`+- **Relicensed from BSD-3-Clause to Apache-2.0.** Apache adds an explicit patent grant and trademark terms, and a `NOTICE` file now carries the copyright (Novavero AI Inc.). Earlier releases on Hackage remain under their original licenses.+- **Source is now ASCII-only**, enforced by a CI check. Removes some mis-encoded characters from comments and keeps the tree clean and portable.+- Widened the `containers` upper bound to allow `containers` 0.8.+- Server: the landing page wraps the config snippet and reflows the stats grid on small screens.++## 0.4.2.0 - 2026-06-10++- **Signing: public-key derivation and rendering** - new `toPublicKey`   (derive the `PublicKey` for a `SecretKey`) and `renderPublicKey` (render   the `name:base64` trust-anchor format clients put in   `trusted-public-keys`).-- **Signing: `normalizeKeyText`** — strips byte-order marks and surrounding+- **Signing: `normalizeKeyText`** - strips byte-order marks and surrounding   whitespace from key text. The server applies it to `CACHE_API_KEY` and   the signing key file, so keys that picked up a BOM or stray CRLF in   transit load byte-clean instead of silently failing auth or signing.@@ -18,20 +25,20 @@ - Removed the CI seed action and its README section; native pushing from   nova-nix replaces it. -## 0.4.1.1 — 2026-06-10+## 0.4.1.1 - 2026-06-10  - Documentation-only release: the 0.4.1.0 entry below now records everything   that release actually contained. No code changes. -## 0.4.1.0 — 2026-06-10+## 0.4.1.0 - 2026-06-10 -- **Server: landing page at `GET /`** — a human-facing page with live stats+- **Server: landing page at `GET /`** - a human-facing page with live stats   (store-path count, signing status, priority), the substituter snippet, and   protocol endpoint documentation. The cache protocol routes are unchanged;   the page is briefly cacheable (`max-age=300, must-revalidate`) since its   stats change as paths are added. - **Correctness and hardening sweep** (a post-0.4.0.0 audit; note: shipped in-  this release without individual entries at tag time — recorded here for the+  this release without individual entries at tag time - recorded here for the   honest history):   - `Base32.decode` rejects non-canonical encodings (overflow bits must be zero).   - NAR deserialisation is strict on untrusted input: trailing bytes rejected,@@ -50,114 +57,114 @@ - **Breaking (should have been a major bump; noted for transparency):**   `getCacheInfo` returns a `CacheInfo` record instead of a tuple. -## 0.4.0.0 — 2026-06-08+## 0.4.0.0 - 2026-06-08  ### Breaking changes -- **`NarInfo` drops the `niSystem` field** — `System` is not part of the Nix+- **`NarInfo` drops the `niSystem` field** - `System` is not part of the Nix   narinfo wire format (Nix ignores unknown keys), so it is removed from the   exported record and `renderNarInfo` no longer emits a `System:` line.  ### Security -- **API key required for writes** — the server refuses to start when+- **API key required for writes** - the server refuses to start when   `CACHE_API_KEY` is unset, unless `--allow-open-writes` is passed explicitly,   so a missing environment variable can no longer leave the cache   world-writable.-- **Streaming request-body cap** — request bodies are read in bounded chunks+- **Streaming request-body cap** - request bodies are read in bounded chunks   with a running size check, so an unsized (chunked) upload can no longer   buffer past the 100 MB limit into memory before it is rejected.-- **Stricter path validation** — `sanitizePath` now accepts only+- **Stricter path validation** - `sanitizePath` now accepts only   `[A-Za-z0-9._+-]` names that are neither dotfiles nor Windows reserved device-  names (`nul`, `con`, `com1`…), rejecting device access, alternate-data-stream+  names (`nul`, `con`, `com1`...), rejecting device access, alternate-data-stream   syntax, and the temporary-write prefix in addition to traversal.-- **No internal detail in error responses** — store reads tolerate I/O errors+- **No internal detail in error responses** - store reads tolerate I/O errors   (treated as absence) and any uncaught handler exception maps to a plain 500,   so filesystem paths and error text are never returned to clients.  ### Bug fixes -- **CRLF-tolerant narinfo parsing** — lines are split on the first colon with a+- **CRLF-tolerant narinfo parsing** - lines are split on the first colon with a   trailing carriage return stripped, so narinfo produced by other tools (or   with `\r\n` line endings) parses correctly instead of corrupting text fields   or rejecting valid integer fields. -## 0.3.2.1 — 2026-03-18+## 0.3.2.1 - 2026-03-18  - Replace partial `decodeUtf8` with total `decodeLatin1` in `Base64.encode`-  and `Signing.sign` — provably safe on base64 ASCII output, eliminates last+  and `Signing.sign` - provably safe on base64 ASCII output, eliminates last   partial function usage - Fix redundant `T.breakOn` call in `NarInfo.parseLine` - No API changes -## 0.3.2.0 — 2026-03-18+## 0.3.2.0 - 2026-03-18  ### Server logging -- **Request logging** — Apache Combined format via `wai-extra` middleware,+- **Request logging** - Apache Combined format via `wai-extra` middleware,   configurable with `LOG_REQUESTS=0` to disable.-- **Error logging** — auth rejections, validation failures, oversized uploads,+- **Error logging** - auth rejections, validation failures, oversized uploads,   and bad paths now logged to stderr with request method and path context.-- **`withLimitedBody` combinator** — extracted common body-limiting pattern from+- **`withLimitedBody` combinator** - extracted common body-limiting pattern from   PUT handlers, reducing duplication.-- **`notFound` named constant** — replaces three inline 404 responses.+- **`notFound` named constant** - replaces three inline 404 responses. - Fixed `loadSigningKey` warnings going to stdout instead of stderr.  ### Seed action fixes -- **Resolve runtime outputs, not derivations** — `nix-instantiate` replaced+- **Resolve runtime outputs, not derivations** - `nix-instantiate` replaced   with `nix-build --no-out-link` so the cache stores actual binaries instead of   `.drv` build recipes.-- **Filter uploads to diff-only** — `nix copy` exports transitive deps; uploads+- **Filter uploads to diff-only** - `nix copy` exports transitive deps; uploads   now filtered against the missing-hashes diff to avoid re-uploading cached paths.-- **Fix broken pipe** — `find | xargs` with `pipefail` caused spurious failures;+- **Fix broken pipe** - `find | xargs` with `pipefail` caused spurious failures;   now writes to intermediate files.-- **Fix xargs line-too-long** — large path lists passed via file instead of inline.-- **Diagnostic output** — upload failure HTTP codes now printed; `nix copy` errors+- **Fix xargs line-too-long** - large path lists passed via file instead of inline.+- **Diagnostic output** - upload failure HTTP codes now printed; `nix copy` errors   no longer silenced.-- **Parallelism** — per-upload `--max-time` added (120s for NARs, 30s for narinfos).+- **Parallelism** - per-upload `--max-time` added (120s for NARs, 30s for narinfos).  ### Server validation -- **Reject derivation narinfos** — `validateNarInfo` now rejects any narinfo+- **Reject derivation narinfos** - `validateNarInfo` now rejects any narinfo   where StorePath ends in `.drv` with a new `DerivationStorePath` error. Binary   caches serve build outputs, not build recipes. - 1 new test (75 total) -## 0.3.1.0 — 2026-03-07+## 0.3.1.0 - 2026-03-07  ### Drop `memory` dependency, use `ram` -- **`memory` → `ram`** — Replaced `memory` package with `ram` (modern, minimal replacement). Same `Data.ByteArray` API, drops the heavy `basement` transitive dependency. Fixes build failure with `crypton >= 1.1` which switched from `memory` to `ram` internally — `Data.ByteArray.ByteArrayAccess` instances were no longer compatible across packages.-- **`crypton >= 1.1` required** — Lower bound bumped from `1.0` to `1.1` so that `crypton` and `nova-cache` both link against `ram` for `ByteArrayAccess` instances. Older `crypton` versions used `memory`, causing instance mismatches at link time.-- All imports remain `Data.ByteArray` — no source-level changes needed for downstream consumers.+- **`memory` -> `ram`** - Replaced `memory` package with `ram` (modern, minimal replacement). Same `Data.ByteArray` API, drops the heavy `basement` transitive dependency. Fixes build failure with `crypton >= 1.1` which switched from `memory` to `ram` internally - `Data.ByteArray.ByteArrayAccess` instances were no longer compatible across packages.+- **`crypton >= 1.1` required** - Lower bound bumped from `1.0` to `1.1` so that `crypton` and `nova-cache` both link against `ram` for `ByteArrayAccess` instances. Older `crypton` versions used `memory`, causing instance mismatches at link time.+- All imports remain `Data.ByteArray` - no source-level changes needed for downstream consumers. -## 0.3.0.0 — 2026-02-28+## 0.3.0.0 - 2026-02-28  ### Breaking changes  - **`decompressXz`** signature changed from `ByteString -> ByteString` to-  `ByteString -> IO (Either String ByteString)` — catches lzma exceptions+  `ByteString -> IO (Either String ByteString)` - catches lzma exceptions   instead of crashing on malformed input-- **`writeNarInfo`** / **`writeNar`** now return `IO Bool` instead of `IO ()` —+- **`writeNarInfo`** / **`writeNar`** now return `IO Bool` instead of `IO ()` -   `False` indicates a rejected path (traversal, empty)  ### Security -- **Request body size limit** — PUT endpoints reject bodies over 100 MB with+- **Request body size limit** - PUT endpoints reject bodies over 100 MB with   413 Payload Too Large (prevents memory exhaustion)-- **Constant-time auth comparison** — API key check uses `constEq` from+- **Constant-time auth comparison** - API key check uses `constEq` from   `Data.ByteArray` instead of `==` (prevents timing side-channel)  ### Bug fixes -- **Safe UTF-8 decoding in NAR deserializer** — `decodeUtf8` replaced with+- **Safe UTF-8 decoding in NAR deserializer** - `decodeUtf8` replaced with   `decodeUtf8'`; malformed UTF-8 in symlink targets or directory entry names   now returns a parse error instead of throwing  ### New features -- **`GET /narinfo-hashes`** endpoint — returns all cached narinfo hashes as+- **`GET /narinfo-hashes`** endpoint - returns all cached narinfo hashes as   newline-delimited text, enabling efficient cache diffing - **`listNarInfoHashes`** function added to `NovaCache.Store` @@ -173,24 +180,24 @@ - Seed action: replaced per-path HEAD checks with single `GET /narinfo-hashes`   call and local diff for dramatically faster cache seeding -## 0.2.4.1 — 2026-02-26+## 0.2.4.1 - 2026-02-26  - License changed from MIT to BSD-3-Clause -## 0.2.4.0 — 2026-02-25+## 0.2.4.0 - 2026-02-25 -- New module: `NovaCache.Base64` — base64 encode/decode re-exported so+- New module: `NovaCache.Base64` - base64 encode/decode re-exported so   downstream consumers don't need a direct `base64-bytestring` dependency - No changes to existing modules -## 0.2.3.0 — 2026-02-23+## 0.2.3.0 - 2026-02-23 -- Drop `unix` dependency — `checkExecutable` now uses cross-platform+- Drop `unix` dependency - `checkExecutable` now uses cross-platform   `System.Directory.getPermissions` instead of `System.Posix.Files` - Fixes Windows build (the `unix` package is not available on Windows) - No API changes -## 0.2.2.0 — 2026-02-23+## 0.2.2.0 - 2026-02-23  - Gate `NovaCache.Compression` and `lzma` dependency behind a `compression`   cabal flag (default on, backwards compatible)@@ -199,9 +206,9 @@ - Compression tests moved to separate test suite (`nova-cache-compression-test`) - No changes to any library source modules -## 0.2.1.0 — 2026-02-22+## 0.2.1.0 - 2026-02-22 -- Server: `validateNarInfo` wired into PUT handler — rejects malformed uploads+- 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@@ -211,22 +218,22 @@   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+## 0.2.0.0 - 2026-02-22 -- New module: `NovaCache.Validate` — pure protocol validation layer+- New module: `NovaCache.Validate` - pure protocol validation layer - `ValidationError` sum type with 10 constructors covering sizes, store paths,   hash formats, content hashes, and Ed25519 signatures-- `validateNarInfo` — field semantic validation (non-negative sizes, parseable+- `validateNarInfo` - field semantic validation (non-negative sizes, parseable   store paths/hashes/references), collects all errors instead of short-circuiting-- `validateNarHash` / `validateFileHash` — SHA-256 content hash verification+- `validateNarHash` / `validateFileHash` - SHA-256 content hash verification   against declared narinfo values-- `validateSignature` — Ed25519 signature verification against a trusted public+- `validateSignature` - Ed25519 signature verification against a trusted public   key (at-least-one semantics, matching Nix behaviour)-- `validateFull` — composes all four stages, collecting errors across all+- `validateFull` - composes all four stages, collecting errors across all - 17 new tests (74 total across 9 groups) - No new dependencies -## 0.1.0.0 — 2026-02-21+## 0.1.0.0 - 2026-02-21  - Initial release (renamed from gb-nix-cache) - Nix-base32 encoding/decoding
LICENSE view
@@ -1,29 +1,202 @@-Copyright (c) 2026 Novavero AI -All rights reserved.+                                 Apache License+                           Version 2.0, January 2004+                        http://www.apache.org/licenses/ -Redistribution and use in source and binary forms, with or without-modification, are permitted provided that the following conditions are met:+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -1. Redistributions of source code must retain the above copyright notice,-   this list of conditions and the following disclaimer.+   1. Definitions. -2. Redistributions in binary form must reproduce the above copyright notice,-   this list of conditions and the following disclaimer in the documentation-   and/or other materials provided with the distribution.+      "License" shall mean the terms and conditions for use, reproduction,+      and distribution as defined by Sections 1 through 9 of this document. -3. Neither the name of the copyright holder nor the names of its-   contributors may be used to endorse or promote products derived from-   this software without specific prior written permission.+      "Licensor" shall mean the copyright owner or entity authorized by+      the copyright owner that is granting the License. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF-THE POSSIBILITY OF SUCH DAMAGE.+      "Legal Entity" shall mean the union of the acting entity and all+      other entities that control, are controlled by, or are under common+      control with that entity. For the purposes of this definition,+      "control" means (i) the power, direct or indirect, to cause the+      direction or management of such entity, whether by contract or+      otherwise, or (ii) ownership of fifty percent (50%) or more of the+      outstanding shares, or (iii) beneficial ownership of such entity.++      "You" (or "Your") shall mean an individual or Legal Entity+      exercising permissions granted by this License.++      "Source" form shall mean the preferred form for making modifications,+      including but not limited to software source code, documentation+      source, and configuration files.++      "Object" form shall mean any form resulting from mechanical+      transformation or translation of a Source form, including but+      not limited to compiled object code, generated documentation,+      and conversions to other media types.++      "Work" shall mean the work of authorship, whether in Source or+      Object form, made available under the License, as indicated by a+      copyright notice that is included in or attached to the work+      (an example is provided in the Appendix below).++      "Derivative Works" shall mean any work, whether in Source or Object+      form, that is based on (or derived from) the Work and for which the+      editorial revisions, annotations, elaborations, or other modifications+      represent, as a whole, an original work of authorship. For the purposes+      of this License, Derivative Works shall not include works that remain+      separable from, or merely link (or bind by name) to the interfaces of,+      the Work and Derivative Works thereof.++      "Contribution" shall mean any work of authorship, including+      the original version of the Work and any modifications or additions+      to that Work or Derivative Works thereof, that is intentionally+      submitted to Licensor for inclusion in the Work by the copyright owner+      or by an individual or Legal Entity authorized to submit on behalf of+      the copyright owner. For the purposes of this definition, "submitted"+      means any form of electronic, verbal, or written communication sent+      to the Licensor or its representatives, including but not limited to+      communication on electronic mailing lists, source code control systems,+      and issue tracking systems that are managed by, or on behalf of, the+      Licensor for the purpose of discussing and improving the Work, but+      excluding communication that is conspicuously marked or otherwise+      designated in writing by the copyright owner as "Not a Contribution."++      "Contributor" shall mean Licensor and any individual or Legal Entity+      on behalf of whom a Contribution has been received by Licensor and+      subsequently incorporated within the Work.++   2. Grant of Copyright License. Subject to the terms and conditions of+      this License, each Contributor hereby grants to You a perpetual,+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable+      copyright license to reproduce, prepare Derivative Works of,+      publicly display, publicly perform, sublicense, and distribute the+      Work and such Derivative Works in Source or Object form.++   3. Grant of Patent License. Subject to the terms and conditions of+      this License, each Contributor hereby grants to You a perpetual,+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable+      (except as stated in this section) patent license to make, have made,+      use, offer to sell, sell, import, and otherwise transfer the Work,+      where such license applies only to those patent claims licensable+      by such Contributor that are necessarily infringed by their+      Contribution(s) alone or by combination of their Contribution(s)+      with the Work to which such Contribution(s) was submitted. If You+      institute patent litigation against any entity (including a+      cross-claim or counterclaim in a lawsuit) alleging that the Work+      or a Contribution incorporated within the Work constitutes direct+      or contributory patent infringement, then any patent licenses+      granted to You under this License for that Work shall terminate+      as of the date such litigation is filed.++   4. Redistribution. You may reproduce and distribute copies of the+      Work or Derivative Works thereof in any medium, with or without+      modifications, and in Source or Object form, provided that You+      meet the following conditions:++      (a) You must give any other recipients of the Work or+          Derivative Works a copy of this License; and++      (b) You must cause any modified files to carry prominent notices+          stating that You changed the files; and++      (c) You must retain, in the Source form of any Derivative Works+          that You distribute, all copyright, patent, trademark, and+          attribution notices from the Source form of the Work,+          excluding those notices that do not pertain to any part of+          the Derivative Works; and++      (d) If the Work includes a "NOTICE" text file as part of its+          distribution, then any Derivative Works that You distribute must+          include a readable copy of the attribution notices contained+          within such NOTICE file, excluding those notices that do not+          pertain to any part of the Derivative Works, in at least one+          of the following places: within a NOTICE text file distributed+          as part of the Derivative Works; within the Source form or+          documentation, if provided along with the Derivative Works; or,+          within a display generated by the Derivative Works, if and+          wherever such third-party notices normally appear. The contents+          of the NOTICE file are for informational purposes only and+          do not modify the License. You may add Your own attribution+          notices within Derivative Works that You distribute, alongside+          or as an addendum to the NOTICE text from the Work, provided+          that such additional attribution notices cannot be construed+          as modifying the License.++      You may add Your own copyright statement to Your modifications and+      may provide additional or different license terms and conditions+      for use, reproduction, or distribution of Your modifications, or+      for any such Derivative Works as a whole, provided Your use,+      reproduction, and distribution of the Work otherwise complies with+      the conditions stated in this License.++   5. Submission of Contributions. Unless You explicitly state otherwise,+      any Contribution intentionally submitted for inclusion in the Work+      by You to the Licensor shall be under the terms and conditions of+      this License, without any additional terms or conditions.+      Notwithstanding the above, nothing herein shall supersede or modify+      the terms of any separate license agreement you may have executed+      with Licensor regarding such Contributions.++   6. Trademarks. This License does not grant permission to use the trade+      names, trademarks, service marks, or product names of the Licensor,+      except as required for reasonable and customary use in describing the+      origin of the Work and reproducing the content of the NOTICE file.++   7. Disclaimer of Warranty. Unless required by applicable law or+      agreed to in writing, Licensor provides the Work (and each+      Contributor provides its Contributions) on an "AS IS" BASIS,+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or+      implied, including, without limitation, any warranties or conditions+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A+      PARTICULAR PURPOSE. You are solely responsible for determining the+      appropriateness of using or redistributing the Work and assume any+      risks associated with Your exercise of permissions under this License.++   8. Limitation of Liability. In no event and under no legal theory,+      whether in tort (including negligence), contract, or otherwise,+      unless required by applicable law (such as deliberate and grossly+      negligent acts) or agreed to in writing, shall any Contributor be+      liable to You for damages, including any direct, indirect, special,+      incidental, or consequential damages of any character arising as a+      result of this License or out of the use or inability to use the+      Work (including but not limited to damages for loss of goodwill,+      work stoppage, computer failure or malfunction, or any and all+      other commercial damages or losses), even if such Contributor+      has been advised of the possibility of such damages.++   9. Accepting Warranty or Additional Liability. While redistributing+      the Work or Derivative Works thereof, You may choose to offer,+      and charge a fee for, acceptance of support, warranty, indemnity,+      or other liability obligations and/or rights consistent with this+      License. However, in accepting such obligations, You may act only+      on Your own behalf and on Your sole responsibility, not on behalf+      of any other Contributor, and only if You agree to indemnify,+      defend, and hold each Contributor harmless for any liability+      incurred by, or claims asserted against, such Contributor by reason+      of your accepting any such warranty or additional liability.++   END OF TERMS AND CONDITIONS++   APPENDIX: How to apply the Apache License to your work.++      To apply the Apache License to your work, attach the following+      boilerplate notice, with the fields enclosed by brackets "[]"+      replaced with your own identifying information. (Don't include+      the brackets!)  The text should be enclosed in the appropriate+      comment syntax for the file format. We also recommend that a+      file or class name and description of purpose be included on the+      same "printed page" as the copyright notice for easier+      identification within third-party archives.++   Copyright [yyyy] [name of copyright owner]++   Licensed under the Apache License, Version 2.0 (the "License");+   you may not use this file except in compliance with the License.+   You may obtain a copy of the License at++       http://www.apache.org/licenses/LICENSE-2.0++   Unless required by applicable law or agreed to in writing, software+   distributed under the License is distributed on an "AS IS" BASIS,+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+   See the License for the specific language governing permissions and+   limitations under the License.
README.md view
@@ -1,12 +1,12 @@ <div align="center"> <h1>nova-cache</h1> <p><strong>The Nix binary cache protocol, in Haskell.</strong></p>-<p>nix-base32, NAR archives, narinfo, store paths, and Ed25519 signing — with an optional WAI cache server. A pure core; IO is confined to the compression, storage, and server boundaries.</p>+<p>nix-base32, NAR archives, narinfo, store paths, and Ed25519 signing - with an optional WAI cache server. A pure core; IO is confined to the compression, storage, and server boundaries.</p>  [![CI](https://github.com/Novavero-AI/nova-cache/actions/workflows/ci.yml/badge.svg)](https://github.com/Novavero-AI/nova-cache/actions/workflows/ci.yml) [![Hackage](https://img.shields.io/hackage/v/nova-cache.svg)](https://hackage.haskell.org/package/nova-cache) ![GHC](https://img.shields.io/badge/GHC-9.8-purple)-![License](https://img.shields.io/badge/license-BSD--3--Clause-blue)+![License](https://img.shields.io/badge/license-Apache--2.0-blue)  </div> @@ -99,4 +99,4 @@  --- -<p align="center"><sub>BSD-3-Clause · <a href="https://github.com/Novavero-AI">Novavero AI</a></sub></p>+<p align="center"><sub>Apache-2.0 - <a href="https://github.com/Novavero-AI">Novavero AI Inc.</a></sub></p>
exe/Main.hs view
@@ -60,7 +60,7 @@ requestLogEnvVar :: String requestLogEnvVar = "LOG_REQUESTS" --- | Maximum narinfo request body — narinfo is small text, so a tight cap.+-- | Maximum narinfo request body - narinfo is small text, so a tight cap. maxNarInfoBodySize :: Int maxNarInfoBodySize = 4 * 1024 * 1024 -- 4 MB @@ -136,7 +136,7 @@           hPutStrLn stderr $             "FATAL: "               ++ apiKeyEnvVar-              ++ " is not set — refusing to start with unauthenticated writes. "+              ++ " is not set - refusing to start with unauthenticated writes. "               ++ "Set "               ++ apiKeyEnvVar               ++ ", or pass --allow-open-writes to override (not recommended on a public host)."@@ -177,7 +177,7 @@ -- | WAI application implementing the Nix binary cache HTTP protocol. app :: Config -> Application app cfg req respond = case (requestMethod req, pathInfo req) of-  -- GET / — human-facing landing page (the protocol lives at the other routes)+  -- GET / - human-facing landing page (the protocol lives at the other routes)   ("GET", []) -> do     pathCount <- length <$> listNarInfoHashes (cfgStore cfg)     let info = getCacheInfo (cfgStore cfg)@@ -264,7 +264,7 @@   first (T.unlines . map (T.pack . show)) (validateNarInfo ni)  -- | Whether the narinfo's declared StorePath actually carries the requested--- hash — so an authenticated writer cannot store a narinfo describing path X+-- hash - so an authenticated writer cannot store a narinfo describing path X -- under path Y's key (a cache-poisoning / confused-deputy shape). narInfoHashMatches :: Text -> NarInfo -> Bool narInfoHashMatches hashKey ni =@@ -335,7 +335,7 @@  -- | Sign a validated 'NarInfo' if a signing key is configured. ----- With no key, returns the unsigned rendering (intentional — the operator+-- With no key, returns the unsigned rendering (intentional - the operator -- configured none).  With a key, FAILS CLOSED: a signing error returns 'Left' -- so the handler refuses the write rather than persisting an unsigned narinfo -- on a cache that is supposed to sign.@@ -389,8 +389,8 @@  -- | The landing page served at @GET /@.  Static apart from four live -- values (store-path count, store dir, signing status, public key); styled--- to match novavero.ai.  Nothing user-supplied is interpolated — the key--- line is operator configuration — so no escaping is needed.+-- to match novavero.ai.  Nothing user-supplied is interpolated - the key+-- line is operator configuration - so no escaping is needed. landingHtml :: CacheInfo -> Bool -> Maybe Text -> Int -> Text landingHtml info signingEnabled pubKey pathCount =   T.unlines@@ -400,7 +400,7 @@       "<meta charset=\"UTF-8\" />",       "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />",       "<title>cache.novavero.ai - Nix binary cache</title>",-      "<meta name=\"description\" content=\"The Novavero Nix binary cache, serving store paths for nova-nix — the Windows-native Nix.\" />",+      "<meta name=\"description\" content=\"The Novavero Nix binary cache, serving store paths for nova-nix - the Windows-native Nix.\" />",       "<link rel=\"icon\" type=\"image/svg+xml\" href=\"data:image/svg+xml," <> novaveroLogoSvgEscaped <> "\" />",       "<style>",       "* { margin: 0; padding: 0; box-sizing: border-box; }",@@ -413,13 +413,13 @@       ".brand svg { width: 44px; height: 44px; border-radius: 10px; }",       "h1 { color: #fff; font-size: 1.6rem; font-family: ui-monospace, Consolas, monospace; }",       ".tagline { color: #9ca3af; margin-bottom: 2.5rem; }",-      ".stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 2.5rem; }",+      ".stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin-bottom: 2.5rem; }",       ".stat { padding: 16px; border: 1px solid #232733; border-radius: 12px; text-align: center; }",       ".stat .value { color: #fff; font-size: 1.4rem; font-weight: 600; }",       ".stat .label { color: #6b7280; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; }",       "h2 { color: #fff; font-size: 1rem; margin: 2rem 0 0.5rem; }",       "p { font-size: 0.9rem; margin-bottom: 0.75rem; }",-      "pre { background: #0d0f14; border: 1px solid #232733; border-radius: 8px; padding: 14px; overflow-x: auto; margin: 0.75rem 0; }",+      "pre { background: #0d0f14; border: 1px solid #232733; border-radius: 8px; padding: 14px; white-space: pre-wrap; word-break: break-all; margin: 0.75rem 0; }",       "code { font-family: ui-monospace, Consolas, monospace; font-size: 0.85rem; color: #e5e7eb; }",       ".footer { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid #232733; font-size: 0.85rem; color: #6b7280; }",       "</style>",@@ -427,7 +427,7 @@       "<body>",       "<div class=\"container\">",       "<div class=\"brand\">" <> novaveroLogoSvg <> "<h1>cache.novavero.ai</h1></div>",-      "<p class=\"tagline\">Nix binary cache — serving store paths for <a href=\"https://github.com/Novavero-AI/nova-nix\">nova-nix</a>, the Windows-native Nix.</p>",+      "<p class=\"tagline\">Nix binary cache - serving store paths for <a href=\"https://github.com/Novavero-AI/nova-nix\">nova-nix</a>, the Windows-native Nix.</p>",       "<div class=\"stats\">",       "<div class=\"stat\"><div class=\"value\">" <> T.pack (show pathCount) <> "</div><div class=\"label\">store paths</div></div>",       "<div class=\"stat\"><div class=\"value\">" <> (if signingEnabled then "ed25519" else "off") <> "</div><div class=\"label\">signing</div></div>",@@ -493,8 +493,8 @@ textHeaders = [(HTTP.hContentType, "text/plain")]  -- | Content-Type and caching headers for a narinfo response.--- A narinfo body is NOT immutable for a fixed key — re-uploading the same store--- path to add or rotate a signature changes it — so it is cacheable but must+-- A narinfo body is NOT immutable for a fixed key - re-uploading the same store+-- path to add or rotate a signature changes it - so it is cacheable but must -- stay revalidatable (no @immutable@). narInfoHeaders :: HTTP.ResponseHeaders narInfoHeaders =
nova-cache.cabal view
@@ -1,14 +1,15 @@ cabal-version:      3.0 name:               nova-cache-version:            0.4.2.0+version:            0.4.2.1 synopsis:           Pure-first Nix binary cache protocol library description:-  A pure-first library implementing the Nix binary cache protocol —+  A pure-first library implementing the Nix binary cache protocol -   nix-base32, NAR serialization, narinfo parsing, Ed25519 signing, store-  path handling, and content validation — with an optional WAI server.+  path handling, and content validation - with an optional WAI server. -license:            BSD-3-Clause+license:            Apache-2.0 license-file:       LICENSE+copyright:          2026 Novavero AI Inc. author:             Devon Tomlin maintainer:         devon.tomlin@novavero.ai homepage:           https://github.com/Novavero-AI/nova-cache@@ -51,7 +52,7 @@       base                >= 4.16 && < 5     , base64-bytestring   >= 1.2 && < 1.3     , bytestring          >= 0.11 && < 0.13-    , containers          >= 0.6 && < 0.8+    , containers          >= 0.6 && < 0.9     , crypton             >= 1.1 && < 2     , directory           >= 1.3 && < 1.4     , filepath            >= 1.4 && < 1.6
src/NovaCache/NAR.hs view
@@ -211,7 +211,7 @@           pure (NarRegular False contents, final)       | otherwise =           -- 'contents' is mandatory (even an empty file serialises with it), so-          -- a regular node without it is malformed — reject, matching Nix.+          -- a regular node without it is malformed - reject, matching Nix.           Left ("expected 'executable' or 'contents' in regular, got: " ++ show tok)  -- | Parse a symlink node.@@ -376,7 +376,7 @@       -- Not a symlink, directory, or regular file: a special file (FIFO,       -- socket, device) or a path that vanished mid-walk.  Fail loudly rather       -- than fabricating an empty regular (which would silently change the NAR-      -- and its hash) — matching Nix, which aborts on unsupported types.+      -- and its hash) - matching Nix, which aborts on unsupported types.       fail ("serialiseFromPath: not a regular file (special or vanished): " ++ path)  -- | Check whether a file has the executable permission set.
src/NovaCache/Signing.hs view
@@ -91,7 +91,7 @@  -- | Normalize key text picked up from an environment variable or a file: -- drop byte-order marks and surrounding whitespace.  'T.strip' alone is not--- enough — a BOM is not whitespace, so it survives stripping and silently+-- enough - a BOM is not whitespace, so it survives stripping and silently -- corrupts the key.  None of the dropped characters can be legitimate key -- material, so normalizing is always safe. normalizeKeyText :: Text -> Text@@ -121,7 +121,7 @@   sk <- cryptoSecretKey (BS.take ed25519SeedSize secretBytes)   pure PublicKey {pkName = keyName, pkBytes = convert (Ed25519.toPublic sk)} --- | Render a public key in Nix's @name:base64@ trust-anchor format — the+-- | Render a public key in Nix's @name:base64@ trust-anchor format - the -- exact string a client puts in @trusted-public-keys@. renderPublicKey :: PublicKey -> Text renderPublicKey (PublicKey keyName bytes) =
src/NovaCache/Store.hs view
@@ -184,7 +184,7 @@ -- dot and are not a Windows reserved device name. This rejects directory -- separators, @.@\/@..@ traversal, dotfiles (including the temp-write prefix), -- NUL bytes, alternate-data-stream (@name:stream@) syntax, and device names--- like @nul@ — so a client-supplied hash or NAR filename can never escape the+-- like @nul@ - so a client-supplied hash or NAR filename can never escape the -- store directory or resolve to a device, on any platform. sanitizePath :: Text -> Maybe FilePath sanitizePath txt@@ -217,7 +217,7 @@ -- | Write a file atomically via write-to-temp-then-rename. -- -- Writes to a temporary file in the same directory, then renames to--- the target path. On POSIX, @rename@ is atomic — readers see either+-- the target path. On POSIX, @rename@ is atomic - readers see either -- the old content or the new content, never a partial write. -- Cleans up the temporary file on failure. atomicWriteFile :: FilePath -> ByteString -> IO ()
src/NovaCache/Validate.hs view
@@ -2,7 +2,7 @@ -- -- Validates narinfo field semantics (sizes, store paths, hash formats), -- NAR/file content hashes against declared values, and Ed25519 signatures.--- All functions are pure — no IO, no side effects.+-- All functions are pure - no IO, no side effects. module NovaCache.Validate   ( ValidationError (..),     validateNarInfo,@@ -47,7 +47,7 @@     SignatureInvalid !Text   | -- | The narinfo has zero signatures.     NoSignatures-  | -- | StorePath is a derivation (.drv) — binary caches serve build outputs only.+  | -- | StorePath is a derivation (.drv) - binary caches serve build outputs only.     DerivationStorePath !Text   deriving (Eq, Show) @@ -120,7 +120,7 @@ -- ---------------------------------------------------------------------------  -- | Validate signatures against a trusted public key. At least one signature--- must verify (matches Nix behaviour — any trusted key suffices).+-- must verify (matches Nix behaviour - any trusted key suffices). -- Returns 'Left [NoSignatures]' when there are no signatures at all. validateSignature :: PublicKey -> NarInfo -> Either [ValidationError] () validateSignature pk ni
test/Main.hs view
@@ -157,7 +157,7 @@       test "decode invalid character" $         assertLeft "invalid char" (Base32.decode "hello!"),       test "encode length for 32 bytes" $-        -- 32 bytes -> ceil(32*8/5) = ceil(51.2) = 52 chars+        -- 32 bytes gives ceil(32*8/5) = ceil(51.2) = 52 chars         let bs = BS.replicate 32 0x42             encoded = Base32.encode bs          in assertEqual "encoded length" 52 (T.length encoded),