corpus

Log | Files | Refs | README | LICENSE

commit 7863ebb170481342f0c267c89717944eb02fce87
parent c80b111cffd5edfeb6a26db5bfad06d948d5be84
Author: mtmn <miro@haravara.org>
Date:   Thu, 30 Apr 2026 23:02:18 +0200

feat: ensure that the builds are deterministic

When moving to Elm I used a quick and dirty hack that was glued together
by a GHA workflow. This was however just way too flaky and had to be
corrected sooner or later.

Diffstat:
M.github/workflows/release-please.yml | 22----------------------
Mflake.nix | 383++++++++++++++++++++++++++++++++++++++-----------------------------------------
Mjustfile | 6+++---
3 files changed, 186 insertions(+), 225 deletions(-)

diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml @@ -18,25 +18,3 @@ jobs: id: release with: token: ${{ secrets.GITHUB_TOKEN }} - - update-nix-hash: - runs-on: ubuntu-latest - needs: release-please - if: ${{ needs.release-please.outputs.prs_created == 'true' }} - steps: - - uses: DeterminateSystems/nix-installer-action@da36cb69b1c3247ad7a1f931ebfd954a1105ef14 # v14 - - name: Update npmDepsHash - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BRANCH: ${{ needs.release-please.outputs.pr_branch }} - run: | - git clone --depth=1 --branch="$BRANCH" \ - "https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git" . - hash=$(nix run nixpkgs#prefetch-npm-deps -- package-lock.json) - sed -i "s|npmDepsHash = \"sha256-[^\"]*\"|npmDepsHash = \"$hash\"|" flake.nix - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add flake.nix - git diff --staged --quiet || git commit -m "chore: update npmDepsHash" - git push - diff --git a/flake.nix b/flake.nix @@ -3,226 +3,209 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; flake-utils.url = "github:numtide/flake-utils"; }; - outputs = { self, nixpkgs, flake-utils, ... }: - flake-utils.lib.eachDefaultSystem ( - system: let - pkgs = nixpkgs.legacyPackages.${system}; - - duckdbPrebuilt = pkgs.fetchurl { - url = "https://npm.duckdb.org/duckdb/duckdb-v1.4.4-node-v137-linux-x64.tar.gz"; - hash = "sha256-Z91EJB81gRaZoLDjkw5lVVQ+jtGIvMV0tMJeOAY7Q3g="; - }; - - spagoRegistry = pkgs.fetchFromGitHub { - owner = "purescript"; - repo = "registry"; - rev = "19f237b9f13c7b286ce2e7129d25d575db787bee"; - hash = "sha256-xC8+jSfKM/MmdmAqRQ44ccRFTAvpwAnnAVs4IRoDhL4="; - }; - - spagoRegistryIndex = pkgs.fetchFromGitHub { - owner = "purescript"; - repo = "registry-index"; - rev = "77163eff5ea12e25d7391cf52fe5b9178145d843"; - hash = "sha256-ez73ecGmzf5d7EkamNZ9KT8u7e4yR7jSvdiGu4bGAHs="; - }; - - elmDeps = pkgs.stdenv.mkDerivation { - name = "corpus-elm-deps"; - - src = pkgs.lib.cleanSourceWith { - src = self; - filter = name: _type: let - baseName = baseNameOf (toString name); - in - pkgs.lib.elem baseName ["elm.json" "src" "Client.elm" "Api.elm" "State.elm" "Types.elm" "View.elm"]; - }; - - nativeBuildInputs = with pkgs; [elmPackages.elm cacert]; - - outputHashAlgo = "sha256"; - outputHashMode = "recursive"; - outputHash = "sha256-IUscqMJsIHLfPssln4rYeEBLyAHPO+46NA46ZFoaPBQ="; - - buildPhase = '' - export HOME=$TMPDIR - export SSL_CERT_FILE="${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" - export ELM_HOME=$HOME/.elm - - elm make src/Client.elm --output=/dev/null - ''; + flake-utils.lib.eachDefaultSystem (system: let + pkgs = nixpkgs.legacyPackages.${system}; + + duckdbPrebuilt = pkgs.fetchurl { + url = "https://npm.duckdb.org/duckdb/duckdb-v1.4.4-node-v137-linux-x64.tar.gz"; + hash = "sha256-Z91EJB81gRaZoLDjkw5lVVQ+jtGIvMV0tMJeOAY7Q3g="; + }; + + spagoRegistry = pkgs.fetchFromGitHub { + owner = "purescript"; + repo = "registry"; + rev = "19f237b9f13c7b286ce2e7129d25d575db787bee"; + hash = "sha256-xC8+jSfKM/MmdmAqRQ44ccRFTAvpwAnnAVs4IRoDhL4="; + }; + + spagoRegistryIndex = pkgs.fetchFromGitHub { + owner = "purescript"; + repo = "registry-index"; + rev = "77163eff5ea12e25d7391cf52fe5b9178145d843"; + hash = "sha256-ez73ecGmzf5d7EkamNZ9KT8u7e4yR7jSvdiGu4bGAHs="; + }; + + elmRegistry = pkgs.fetchFromGitHub { + owner = "elm"; + repo = "package.elm-lang.org"; + rev = "afe1a128b4bbf5ec0ebc21886d32b0b473794a9e"; + hash = "sha256-aaflueHul/lXA/v8YM5Irckl8+jEUMsvkSS5nh9eYCg="; + }; + + elmDeps = pkgs.stdenv.mkDerivation { + name = "corpus-elm-deps"; - installPhase = '' - mkdir -p $out - cp -r $HOME/.elm/0.19.1/packages $out/ - ''; + src = pkgs.lib.cleanSourceWith { + src = self; + filter = name: _type: let + baseName = baseNameOf (toString name); + in + pkgs.lib.elem baseName + [ + "elm.json" + "src" + "Client.elm" + "Api.elm" + "State.elm" + "Types.elm" + "View.elm" + ]; }; - spagoDeps = pkgs.stdenv.mkDerivation { - name = "corpus-spago-deps"; - - src = pkgs.lib.cleanSourceWith { - src = self; - filter = name: _type: let - baseName = baseNameOf (toString name); - in - pkgs.lib.elem baseName ["package.json" "package-lock.json" "spago.yaml" "spago.lock"]; - }; - - nativeBuildInputs = with pkgs; [nodejs git cacert purescript]; + nativeBuildInputs = with pkgs; [elmPackages.elm cacert]; - outputHashAlgo = "sha256"; - outputHashMode = "recursive"; - outputHash = "sha256-dBorhgF/CIngAe+6fvvGHnGkQsk070SDZbgF5xTGx9c="; + outputHashAlgo = "sha256"; + outputHashMode = "recursive"; + outputHash = "sha256-3D0PJQH1AkHdt7eGmbplZBX1FXmS2hb36enYU7ajqY4="; - buildPhase = '' - export HOME=$TMPDIR - export SSL_CERT_FILE="${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" + buildPhase = '' + export HOME=$TMPDIR + export SSL_CERT_FILE="${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" + export ELM_HOME=$HOME/.elm - # Pre-populate registry so spago doesn't clone from GitHub - mkdir -p $HOME/.cache/spago-nodejs - cp -r ${spagoRegistry} $HOME/.cache/spago-nodejs/registry - cp -r ${spagoRegistryIndex} $HOME/.cache/spago-nodejs/registry-index - chmod -R u+w $HOME/.cache/spago-nodejs + # Populate the Elm package registry from the fetched snapshot. + mkdir -p $ELM_HOME/0.19.1/registry + cp -r ${elmRegistry}/* $ELM_HOME/0.19.1/registry/ - # We need the local spago version to ensure compatibility - npm ci --ignore-scripts - patchShebangs node_modules + elm make src/Client.elm --output=/dev/null + ''; - # Use the purs provided by Nix - mkdir -p node_modules/.bin - ln -sf ${pkgs.purescript}/bin/purs node_modules/.bin/purs + installPhase = '' + mkdir -p $out + cp -r $HOME/.elm/0.19.1/packages $out/ + ''; + }; - npx spago install - ''; - - installPhase = '' - mkdir -p $out - cp -r .spago/p $out/packages - ''; - }; + spagoDeps = pkgs.stdenv.mkDerivation { + name = "corpus-spago-deps"; src = pkgs.lib.cleanSourceWith { src = self; - filter = name: type: let + filter = name: _type: let baseName = baseNameOf (toString name); in - !( - (type == "directory" && (baseName == "docs" || baseName == ".git")) - || (type - == "regular" - && ( - pkgs.lib.hasSuffix ".md" baseName - || baseName == "justfile" - || baseName == "flake.nix" - || baseName == "flake.lock" - )) - ); - }; - - corpus = pkgs.buildNpmPackage { - pname = "corpus"; - inherit ((builtins.fromJSON (builtins.readFile ./package.json))) version; - inherit src; - - npmDepsHash = "sha256-uwag7hUAc4zKsXNAyPIfvl7ReTQm8H5s0y1UOhDjZDg="; - npmRebuildFlags = ["--ignore-scripts"]; - - nativeBuildInputs = with pkgs; [ - makeWrapper - nodejs - git - purescript - elmPackages.elm - ]; - - buildPhase = '' - export HOME="$TMPDIR" - - # Copy duckdb bindings to avoid native extension compilation - mkdir -p node_modules/duckdb/lib/binding - tar -xf ${duckdbPrebuilt} -C node_modules/duckdb/lib/binding --strip-components=1 - - # Restore spago packages - mkdir -p .spago - cp -r ${spagoDeps}/packages .spago/p - chmod -R u+w .spago - - # Provide pinned registry so spago finds package-sets - mkdir -p $HOME/.cache/spago-nodejs - cp -r ${spagoRegistry} $HOME/.cache/spago-nodejs/registry - cp -r ${spagoRegistryIndex} $HOME/.cache/spago-nodejs/registry-index - chmod -R u+w $HOME/.cache/spago-nodejs - - # Restore elm packages - mkdir -p $HOME/.elm/0.19.1 - cp -r ${elmDeps}/packages $HOME/.elm/0.19.1/ - chmod -R u+w $HOME/.elm - - npm run release - ''; - - installPhase = '' - mkdir -p $out/lib/corpus - cp -r server.js client.js users.json package.json node_modules assets $out/lib/corpus/ - if [ -f .env ]; then - cp .env $out/lib/corpus/ - else - cp .env.example $out/lib/corpus/.env - fi - - mkdir -p $out/bin - makeWrapper ${pkgs.nodejs}/bin/node $out/bin/corpus-server \ - --add-flags "--no-deprecation" \ - --add-flags "$out/lib/corpus/server.js" \ - --chdir "$out/lib/corpus" - ''; - }; - in { - packages = { - default = corpus; - inherit elmDeps spagoDeps; - container = pkgs.dockerTools.buildLayeredImage { - name = "corpus"; - tag = "latest"; - contents = [pkgs.nodejs pkgs.cacert]; - config = { - Cmd = ["${corpus}/bin/corpus-server"]; - WorkingDir = "/app"; - ExposedPorts = { - "8321/tcp" = {}; - }; - User = "1000:1000"; - Env = [ - "PORT=8321" - "NODE_ENV=production" - ]; - }; - fakeRootCommands = '' - mkdir -p /app/data - chown -R 1000:1000 /app - chmod 700 /app/data - ''; - enableFakechroot = true; - }; + pkgs.lib.elem baseName + [ + "package.json" + "package-lock.json" + "spago.yaml" + "spago.lock" + ]; }; - devShells.default = pkgs.mkShell { - buildInputs = with pkgs; [ - nodejs - purescript - elmPackages.elm - awscli2 - duckdb - esbuild - ]; - }; - } - ); + nativeBuildInputs = with pkgs; [nodejs git cacert purescript]; + + outputHashAlgo = "sha256"; + outputHashMode = "recursive"; + outputHash = "sha256-dBorhgF/CIngAe+6fvvGHnGkQsk070SDZbgF5xTGx9c="; + + buildPhase = '' + export HOME=$TMPDIR + export SSL_CERT_FILE="${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" + + # Pre‑populate registry so spago doesn't clone from GitHub + mkdir -p $HOME/.cache/spago-nodejs + cp -r ${spagoRegistry} $HOME/.cache/spago-nodejs/registry + cp -r ${spagoRegistryIndex} $HOME/.cache/spago-nodejs/registry-index + chmod -R u+w $HOME/.cache/spago-nodejs + + npm ci --ignore-scripts + patchShebangs node_modules + mkdir -p node_modules/.bin + ln -sf ${pkgs.purescript}/bin/purs node_modules/.bin/purs + npx spago install + ''; + + installPhase = '' + mkdir -p $out + cp -r .spago/p $out/packages + ''; + }; + + src = pkgs.lib.cleanSourceWith { + src = self; + filter = name: type: let + baseName = baseNameOf (toString name); + in + !((type == "directory" && (baseName == "docs" || baseName == ".git")) + || (type + == "regular" + && ( + pkgs.lib.hasSuffix ".md" baseName + || baseName == "justfile" + || baseName == "flake.nix" + || baseName == "flake.lock" + ))); + }; + + corpus = pkgs.buildNpmPackage { + pname = "corpus"; + inherit ((builtins.fromJSON (builtins.readFile ./package.json))) version; + inherit src; + + npmDepsHash = "sha256-uwag7hUAc4zKsXNAyPIfvl7ReTQm8H5s0y1UOhDjZDg="; + npmRebuildFlags = ["--ignore-scripts"]; + + nativeBuildInputs = with pkgs; [ + makeWrapper + nodejs + git + purescript + elmPackages.elm + ]; + + buildPhase = '' + export HOME="$TMPDIR" + + # DuckDB bindings + mkdir -p node_modules/duckdb/lib/binding + tar -xf ${duckdbPrebuilt} -C node_modules/duckdb/lib/binding --strip-components=1 + + # Spago packages + mkdir -p .spago + cp -r ${spagoDeps}/packages .spago/p + chmod -R u+w .spago + + # Spago registry cache + mkdir -p $HOME/.cache/spago-nodejs + cp -r ${spagoRegistry} $HOME/.cache/spago-nodejs/registry + cp -r ${spagoRegistryIndex} $HOME/.cache/spago-nodejs/registry-index + chmod -R u+w $HOME/.cache/spago-nodejs + + # Elm packages + mkdir -p $HOME/.elm/0.19.1 + cp -r ${elmDeps}/packages $HOME/.elm/0.19.1/ + chmod -R u+w $HOME/.elm + + npm run release + ''; + + installPhase = '' + mkdir -p $out/lib/corpus + cp -r server.js client.js users.json package.json node_modules assets $out/lib/corpus/ + if [ -f .env ]; then + cp .env $out/lib/corpus/ + else + cp .env.example $out/lib/corpus/.env + fi + + mkdir -p $out/bin + makeWrapper ${pkgs.nodejs}/bin/node $out/bin/corpus-server \ + --add-flags "--no-deprecation" \ + --add-flags "$out/lib/corpus/server.js" \ + --chdir "$out/lib/corpus" + ''; + }; + in { + packages = { + default = corpus; + inherit elmDeps spagoDeps; + }; + }); } diff --git a/justfile b/justfile @@ -3,16 +3,16 @@ help: # Setup npm and spago -setup: generate-users-json +setup: npm install npx spago install # Build the project locally -build: generate-users-json +build: npm run build # Build a release -release: generate-users-json +release: npm run release # Run the project locally