From 5477cfc71b6c7e3843cdacc202c904fcfb3a5e52 Mon Sep 17 00:00:00 2001 From: m3tam3re Date: Sun, 26 Jul 2026 12:09:22 +0200 Subject: [PATCH] feat: add automatic n8n overlay updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - overlays/mods/n8n.nix: add passthru.updateScript pointing to ./update.sh - overlays/mods/update.sh: self-contained updater that fetches the latest stable n8n-io/n8n release, recomputes both src and pnpmDeps hashes (src via nix-prefetch-url with --name workaround for the `@` in the tag, pnpmDeps via the lib.fakeHash mismatch trick) - .gitea/workflows/nix-update.yml: discover and run overlays/mods/*/update.sh after the pkgs/ loop; verify step skips overlay/* entries (covered by flake check) The test suite (tests/n8n-overlay-test.nix) forbids exporting a local n8n package, so the overlay cannot be exposed via pkgs/n8n/ — the workflow gets an explicit overlay-discovery block instead. --- .gitea/workflows/nix-update.yml | 47 ++++++++++ overlays/mods/n8n.nix | 5 ++ overlays/mods/update.sh | 153 ++++++++++++++++++++++++++++++++ 3 files changed, 205 insertions(+) create mode 100755 overlays/mods/update.sh diff --git a/.gitea/workflows/nix-update.yml b/.gitea/workflows/nix-update.yml index 332b429..9bea6ce 100644 --- a/.gitea/workflows/nix-update.yml +++ b/.gitea/workflows/nix-update.yml @@ -352,6 +352,47 @@ jobs: done fi + # ── Update overlay modifications (overlays/mods/) ─────────────── + # Overlay updates live outside pkgs/ because the test suite + # (tests/n8n-overlay-test.nix) forbids exporting the overlay target + # as a local package. Each `overlays/mods/*/update.sh` or + # `overlays/mods/update.sh` is a self-contained updater that handles + # version discovery, hash recomputation, and commit on its own. + if [ -z "${{ inputs.package }}" ]; then + echo "" + echo "🔍 Discovering overlay update scripts in overlays/mods/ ..." + OVERLAY_SCRIPTS=$(find overlays/mods -maxdepth 2 -name "update.sh" 2>/dev/null | sort) + + if [ -n "$OVERLAY_SCRIPTS" ]; then + for script in $OVERLAY_SCRIPTS; do + OVERLAY_NAME=$(basename "$(dirname "$script")") + echo "::group::Updating overlay: $OVERLAY_NAME" + BEFORE_HASH=$(git rev-parse HEAD) + + # Overlay scripts are self-contained and use nix-shell shebangs + # for their dependencies. They commit on their own. + if bash "$script" 2>&1 | tee /tmp/update-overlay-${OVERLAY_NAME}.log; then + if [ "$(git rev-parse HEAD)" != "$BEFORE_HASH" ]; then + echo "✅ Updated overlay/$OVERLAY_NAME" + UPDATES_FOUND=true + if [ -n "$UPDATED_PACKAGES" ]; then + UPDATED_PACKAGES="$UPDATED_PACKAGES, overlay/$OVERLAY_NAME" + else + UPDATED_PACKAGES="overlay/$OVERLAY_NAME" + fi + else + echo "✓ overlay/$OVERLAY_NAME already up to date" + fi + else + echo "⚠️ Update failed for overlay/$OVERLAY_NAME" + fi + echo "::endgroup::" + done + else + echo "ℹ️ No overlay update scripts found." + fi + fi + COMMIT_COUNT=$(git rev-list --count origin/master..HEAD) if [ "$COMMIT_COUNT" -gt 0 ]; then @@ -382,6 +423,12 @@ jobs: SUCCESSFUL_PACKAGES=() for pkg in "${PKGS[@]}"; do + # Overlay updates (e.g. "overlay/n8n") have no top-level flake + # attribute — `nix flake check` already verifies them above. + if [[ "$pkg" == overlay/* ]]; then + echo "⊘ Skipping per-package build for $pkg (verified via flake check)" + continue + fi echo "::group::Building $pkg" if nix build .#$pkg 2>&1 | tee /tmp/build-${pkg}.log; then echo "✅ Build successful for $pkg" diff --git a/overlays/mods/n8n.nix b/overlays/mods/n8n.nix index 8334ccb..510402b 100644 --- a/overlays/mods/n8n.nix +++ b/overlays/mods/n8n.nix @@ -39,6 +39,11 @@ in fi ''; + # Self-contained update script (./update.sh) — fetches latest stable + # release from n8n-io/n8n, recomputes both src and pnpmDeps hashes. + # The CI workflow in .gitea/workflows/nix-update.yml discovers and runs it. + passthru = (previousAttrs.passthru or {}) // {updateScript = ./update.sh;}; + meta = previousAttrs.meta // { diff --git a/overlays/mods/update.sh b/overlays/mods/update.sh new file mode 100755 index 0000000..63d4b8d --- /dev/null +++ b/overlays/mods/update.sh @@ -0,0 +1,153 @@ +#!/usr/bin/env nix-shell +#!nix-shell --pure -i bash -p bash curl jq nix nix-prefetch cacert git +set -euo pipefail + +# Update the n8n overlay (overlays/mods/n8n.nix) to the latest stable GitHub +# release of n8n-io/n8n. +# +# This is a self-contained updater (not nix-update), because the overlay file +# is a `prev.n8n.overrideAttrs` form rather than a standalone derivation and +# the test suite (tests/n8n-overlay-test.nix) explicitly forbids exporting a +# local n8n package. The CI workflow discovers and runs this script directly. +# +# What it does, in order: +# 1. Fetch latest stable (non-prerelease) tag from the GitHub API +# 2. Strip the `n8n@` prefix → version +# 3. Compare to the version pinned in overlays/mods/n8n.nix +# 4. Compute new src hash (fetchFromGitHub tarball) via nix-prefetch-url +# 5. Compute new pnpmDeps hash via the lib.fakeHash trick +# 6. Commit + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +NIX_FILE="$SCRIPT_DIR/n8n.nix" + +REPO="n8n-io/n8n" +TAG_PREFIX="n8n@" + +# ── 1. Latest stable release ─────────────────────────────────────────────── +echo "Fetching latest stable release of $REPO ..." +LATEST_TAG=$(curl -fsSL "https://api.github.com/repos/$REPO/releases/latest" \ + | jq -r 'select(.prerelease == false) | .tag_name') + +if [[ -z "$LATEST_TAG" ]]; then + echo "ERROR: No stable release found for $REPO" >&2 + exit 1 +fi + +VERSION="${LATEST_TAG#${TAG_PREFIX}}" +echo "Latest version: $VERSION" + +# ── 2. Current version from the nix file ─────────────────────────────────── +CURRENT=$(awk ' + /^[[:space:]]*version = "/ { + gsub(/^[[:space:]]*version = "/, "") + gsub(/".*/, "") + print + exit + } +' "$NIX_FILE") +echo "Current version: $CURRENT" + +if [[ "$VERSION" == "$CURRENT" ]]; then + echo "Already at latest version, nothing to do." + exit 0 +fi + +echo "==> Updating n8n $CURRENT -> $VERSION" + +# ── 3. Bump version in the file ──────────────────────────────────────────── +sed -i "s|^\([[:space:]]*\)version = \"[^\"]*\";|\1version = \"$VERSION\";|" "$NIX_FILE" + +# ── 4. Compute and apply src hash ────────────────────────────────────────── +echo "==> Computing src hash ..." +SRC_URL="https://github.com/$REPO/archive/refs/tags/${TAG_PREFIX}${VERSION}.tar.gz" +# `--name n8n-source` works around the `@` in the upstream tag name +# (nix-prefetch-url refuses filenames containing `@`). +SRC_HASH=$(nix-prefetch-url --unpack --type sha256 --name n8n-source "$SRC_URL" 2>/dev/null) +SRC_SRI=$(nix hash convert --hash-algo sha256 --to sri "$SRC_HASH") +echo " src hash: $SRC_SRI" + +# Replace the FIRST `hash = "sha256-...";` occurrence (the src block sits above pnpmDeps) +awk -v sri="$SRC_SRI" ' + !src_done && /^[[:space:]]*hash = "sha256-/ { + sub(/"sha256-[A-Za-z0-9+/=]+"/, "\"" sri "\"") + src_done = 1 + } + { print } +' "$NIX_FILE" > "$NIX_FILE.tmp" && mv "$NIX_FILE.tmp" "$NIX_FILE" + +# ── 5. Compute pnpmDeps hash via lib.fakeHash ────────────────────────────── +echo "==> Computing pnpmDeps hash (this may take a while) ..." + +# 5a. Temporarily set the pnpmDeps hash to lib.fakeHash +awk ' + !pnpm_started && /^[[:space:]]*pnpmDeps = / { pnpm_started = 1 } + pnpm_started && !pnpm_done && /^[[:space:]]*hash = "sha256-/ { + sub(/"sha256-[A-Za-z0-9+/=]+"/, "lib.fakeHash") + pnpm_done = 1 + } + { print } +' "$NIX_FILE" > "$NIX_FILE.tmp" && mv "$NIX_FILE.tmp" "$NIX_FILE" + +# 5b. Evaluate n8nModified.pnpmDeps — fixed-output derivation with a wrong hash +# will fail and print the correct hash in the "got:" line. +SYSTEM=$(nix eval --impure --raw --expr 'builtins.currentSystem' 2>/dev/null || echo "x86_64-linux") +NIX_EXPR="(let + flake = builtins.getFlake (toString ./.); + pkgs = flake.inputs.nixpkgs.legacyPackages.\${builtins.currentSystem}; + n8nModified = import ./overlays/mods/n8n.nix { prev = pkgs; }; + in n8nModified.pnpmDeps)" + +BUILD_OUTPUT=$(nix build --impure --no-link --expr "$NIX_EXPR" 2>&1 || true) + +# Hash mismatch errors list two sha256 values: the wrong "Expected"/"specified" +# one (our lib.fakeHash = all A's) and the correct "Got"/"got" one. Different +# nix versions capitalise the label differently, so we filter out the fakeHash +# sentinel and take whatever real sha256 remains. +PNPM_HASH=$(echo "$BUILD_OUTPUT" \ + | grep -oE 'sha256-[A-Za-z0-9+/=]+' \ + | grep -v 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' \ + | tail -1) + +if [[ -z "$PNPM_HASH" ]]; then + echo "ERROR: Failed to compute pnpmDeps hash. nix build output:" >&2 + echo "$BUILD_OUTPUT" >&2 + echo "Restoring original file..." >&2 + git checkout -- "$NIX_FILE" 2>/dev/null || true + exit 1 +fi + +echo " pnpmDeps hash: $PNPM_HASH" + +# 5d. Replace lib.fakeHash with the real hash +awk -v sri="$PNPM_HASH" ' + !pnpm_started && /^[[:space:]]*pnpmDeps = / { pnpm_started = 1 } + pnpm_started && !pnpm_done && /^[[:space:]]*hash = / { + sub(/lib\.fakeHash/, "\"" sri "\"") + pnpm_done = 1 + } + { print } +' "$NIX_FILE" > "$NIX_FILE.tmp" && mv "$NIX_FILE.tmp" "$NIX_FILE" + +# ── 6. Verify the file parses and commit ─────────────────────────────────── +echo "==> Verifying nix evaluation ..." +if ! nix eval --impure --expr " + let + flake = builtins.getFlake (toString ./.); + pkgs = flake.inputs.nixpkgs.legacyPackages.\${builtins.currentSystem}; + in (import ./overlays/mods/n8n.nix { prev = pkgs; }).version +" >/dev/null 2>&1; then + echo "ERROR: nix evaluation failed after update; restoring original." >&2 + git checkout -- "$NIX_FILE" 2>/dev/null || true + exit 1 +fi + +NIXPKGS_ROOT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true) +if [[ -n "$NIXPKGS_ROOT" ]] && \ + [[ -n "$(git -C "$NIXPKGS_ROOT" status --porcelain "$NIX_FILE")" ]]; then + git -C "$NIXPKGS_ROOT" add "$NIX_FILE" + git -C "$NIXPKGS_ROOT" commit -m "n8n: $CURRENT -> $VERSION" + echo "==> Committed" +fi + +echo "==> Done"