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:
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