From bc75505ca3f04965f2fa71cffd1caa3588a0d26b Mon Sep 17 00:00:00 2001 From: m3tm3re Date: Tue, 13 Jan 2026 20:26:30 +0100 Subject: [PATCH] feat: add n8n, beads, opencode packages - Add pkgs/n8n/default.nix (v2.4.1) - Add pkgs/beads/default.nix (v0.47.1, doCheck=false) - Add pkgs/opencode/default.nix (v1.1.18) - Include relax-bun-version-check.patch for opencode - Register all packages in pkgs/default.nix --- pkgs/beads/default.nix | 62 ++++++ pkgs/default.nix | 3 + pkgs/n8n/default.nix | 114 ++++++++++ pkgs/opencode/default.nix | 222 ++++++++++++++++++++ pkgs/opencode/relax-bun-version-check.patch | 28 +++ 5 files changed, 429 insertions(+) create mode 100644 pkgs/beads/default.nix create mode 100644 pkgs/n8n/default.nix create mode 100644 pkgs/opencode/default.nix create mode 100644 pkgs/opencode/relax-bun-version-check.patch diff --git a/pkgs/beads/default.nix b/pkgs/beads/default.nix new file mode 100644 index 0000000..15276bf --- /dev/null +++ b/pkgs/beads/default.nix @@ -0,0 +1,62 @@ +{ + lib, + stdenv, + buildGoModule, + fetchFromGitHub, + gitMinimal, + installShellFiles, + versionCheckHook, + writableTmpDirAsHomeHook, +}: +buildGoModule (finalAttrs: { + pname = "beads"; + version = "0.47.1"; + + src = fetchFromGitHub { + owner = "steveyegge"; + repo = "beads"; + tag = "v${finalAttrs.version}"; + hash = "sha256-DwIR/r1TJnpVd/CT1E2OTkAjU7k9/KHbcVwg5zziFVg="; + }; + + vendorHash = "sha256-pY5m5ODRgqghyELRwwxOr+xlW41gtJWLXaW53GlLaFw="; + + subPackages = ["cmd/bd"]; + + ldflags = ["-s" "-w"]; + + nativeBuildInputs = [installShellFiles]; + + nativeCheckInputs = [gitMinimal writableTmpDirAsHomeHook]; + + # Skip security tests on Darwin - they check for /etc/passwd which isn't available in sandbox + checkFlags = + lib.optionals stdenv.hostPlatform.isDarwin + ["-skip=TestCleanupMergeArtifacts_CommandInjectionPrevention"]; + + preCheck = '' + export PATH="$out/bin:$PATH" + ''; + + postInstall = lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform) '' + installShellCompletion --cmd bd \ + --bash <($out/bin/bd completion bash) \ + --fish <($out/bin/bd completion fish) \ + --zsh <($out/bin/bd completion zsh) + ''; + + nativeInstallCheckInputs = [versionCheckHook writableTmpDirAsHomeHook]; + versionCheckProgramArg = "version"; + doInstallCheck = true; + + # Tests require git worktree operations that fail in Nix sandbox + doCheck = false; + + meta = { + description = "Lightweight memory system for AI coding agents with graph-based issue tracking"; + homepage = "https://github.com/steveyegge/beads"; + license = lib.licenses.mit; + maintainers = with lib.maintainers; [kedry]; + mainProgram = "bd"; + }; +}) diff --git a/pkgs/default.nix b/pkgs/default.nix index 4b93847..d6a729e 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -1,11 +1,14 @@ {pkgs, ...}: { # Custom packages registry # Each package is defined in its own directory under pkgs/ + beads = pkgs.callPackage ./beads {}; code2prompt = pkgs.callPackage ./code2prompt {}; hyprpaper-random = pkgs.callPackage ./hyprpaper-random {}; launch-webapp = pkgs.callPackage ./launch-webapp {}; mem0 = pkgs.callPackage ./mem0 {}; msty-studio = pkgs.callPackage ./msty-studio {}; + n8n = pkgs.callPackage ./n8n {}; + opencode = pkgs.callPackage ./opencode {}; pomodoro-timer = pkgs.callPackage ./pomodoro-timer {}; rofi-project-opener = pkgs.callPackage ./rofi-project-opener {}; stt-ptt = pkgs.callPackage ./stt-ptt {}; diff --git a/pkgs/n8n/default.nix b/pkgs/n8n/default.nix new file mode 100644 index 0000000..9d81f5e --- /dev/null +++ b/pkgs/n8n/default.nix @@ -0,0 +1,114 @@ +{ + stdenv, + lib, + nixosTests, + fetchFromGitHub, + nodejs, + pnpm_10, + fetchPnpmDeps, + pnpmConfigHook, + python3, + node-gyp, + cctools, + xcbuild, + libkrb5, + libmongocrypt, + libpq, + makeWrapper, +}: +stdenv.mkDerivation (finalAttrs: { + pname = "n8n"; + version = "2.4.1"; + + src = fetchFromGitHub { + owner = "n8n-io"; + repo = "n8n"; + tag = "n8n@${finalAttrs.version}"; + hash = "sha256-EQP9ZI8kt30SUYE1+/UUpxQXpavzKqDu8qE24zsNifg="; + }; + + pnpmDeps = fetchPnpmDeps { + inherit (finalAttrs) pname version src; + pnpm = pnpm_10; + fetcherVersion = 3; + hash = "sha256-M/HXRii4FotDC/xIa6DVlY13rCHMq7oZNtPi7hvDXik="; + }; + + nativeBuildInputs = + [ + pnpmConfigHook + pnpm_10 + python3 # required to build sqlite3 bindings + node-gyp # required to build sqlite3 bindings + makeWrapper + ] + ++ lib.optional stdenv.hostPlatform.isDarwin [cctools xcbuild]; + + buildInputs = [nodejs libkrb5 libmongocrypt libpq]; + + buildPhase = '' + runHook preBuild + + pushd node_modules/sqlite3 + node-gyp rebuild + popd + + # TODO: use deploy after resolved https://github.com/pnpm/pnpm/issues/5315 + pnpm build --filter=n8n + + runHook postBuild + ''; + + preInstall = '' + echo "Removing non-deterministic and unnecessary files" + + find -type d -name .turbo -exec rm -rf {} + + rm node_modules/.modules.yaml + rm packages/nodes-base/dist/types/nodes.json + + CI=true pnpm --ignore-scripts prune --prod + find -type f \( -name "*.ts" -o -name "*.map" \) -exec rm -rf {} + + rm -rf node_modules/.pnpm/{typescript*,prettier*} + shopt -s globstar + # https://github.com/pnpm/pnpm/issues/3645 + find node_modules packages/**/node_modules -xtype l -delete + + echo "Removed non-deterministic and unnecessary files" + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out/{bin,lib/n8n} + mv {packages,node_modules} $out/lib/n8n + + makeWrapper $out/lib/n8n/packages/cli/bin/n8n $out/bin/n8n \ + --set N8N_RELEASE_TYPE "stable" + + runHook postInstall + ''; + + passthru = { + tests = nixosTests.n8n; + updateScript = ./update.sh; + }; + + # this package has ~80000 files, these take too long and seem to be unnecessary + dontStrip = true; + dontPatchELF = true; + dontRewriteSymlinks = true; + + meta = { + description = "Free and source-available fair-code licensed workflow automation tool"; + longDescription = '' + Free and source-available fair-code licensed workflow automation tool. + Easily automate tasks across different services. + ''; + homepage = "https://n8n.io"; + changelog = "https://github.com/n8n-io/n8n/releases/tag/n8n@${finalAttrs.version}"; + maintainers = with lib.maintainers; [gepbird AdrienLemaire sweenu]; + license = lib.licenses.sustainableUse; + mainProgram = "n8n"; + platforms = lib.platforms.unix; + }; +}) diff --git a/pkgs/opencode/default.nix b/pkgs/opencode/default.nix new file mode 100644 index 0000000..a3fe1ee --- /dev/null +++ b/pkgs/opencode/default.nix @@ -0,0 +1,222 @@ +{ + lib, + stdenvNoCC, + bun, + fetchFromGitHub, + fzf, + makeBinaryWrapper, + models-dev, + nix-update-script, + ripgrep, + testers, + installShellFiles, + writableTmpDirAsHomeHook, +}: let + pname = "opencode"; + version = "1.1.18"; + src = fetchFromGitHub { + owner = "anomalyco"; + repo = "opencode"; + tag = "v${version}"; + hash = "sha256-3A4s0FpjZuGB0HGMQVBXfWq+0yHmeIvnEQTSX3amV4I="; + }; + + node_modules = stdenvNoCC.mkDerivation { + pname = "${pname}-node_modules"; + inherit version src; + + impureEnvVars = + lib.fetchers.proxyImpureEnvVars + ++ ["GIT_PROXY_COMMAND" "SOCKS_SERVER"]; + + nativeBuildInputs = [bun writableTmpDirAsHomeHook]; + + dontConfigure = true; + + buildPhase = '' + runHook preBuild + + export BUN_INSTALL_CACHE_DIR=$(mktemp -d) + + bun install \ + --cpu="*" \ + --filter=./packages/opencode \ + --force \ + --frozen-lockfile \ + --ignore-scripts \ + --no-progress \ + --os="*" \ + --production + + bun run ./nix/scripts/canonicalize-node-modules.ts + bun run ./nix/scripts/normalize-bun-binaries.ts + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out + find . -type d -name node_modules -exec cp -R --parents {} $out \; + + runHook postInstall + ''; + + # NOTE: Required else we get errors that our fixed-output derivation references store paths + dontFixup = true; + + outputHash = "sha256-zSco4ORQQOqV3vMPuP+M/q/hBa+MJGnTKIlxgngMA3g="; + outputHashAlgo = "sha256"; + outputHashMode = "recursive"; + }; +in + stdenvNoCC.mkDerivation (finalAttrs: { + inherit pname version src node_modules; + + nativeBuildInputs = [ + bun + installShellFiles + makeBinaryWrapper + models-dev + writableTmpDirAsHomeHook + ]; + + patches = [ + # NOTE: Relax Bun version check to be a warning instead of an error + ./relax-bun-version-check.patch + ]; + + dontConfigure = true; + + env.MODELS_DEV_API_JSON = "${models-dev}/dist/_api.json"; + env.OPENCODE_VERSION = finalAttrs.version; + env.OPENCODE_CHANNEL = "stable"; + + buildPhase = '' + runHook preBuild + + # Copy all node_modules including the .bun directory with actual packages + cp -r ${finalAttrs.node_modules}/node_modules . + cp -r ${finalAttrs.node_modules}/packages . + + ( + cd packages/opencode + + # Fix symlinks to workspace packages + chmod -R u+w ./node_modules + mkdir -p ./node_modules/@opencode-ai + rm -f ./node_modules/@opencode-ai/{script,sdk,plugin} + ln -s $(pwd)/../../packages/script ./node_modules/@opencode-ai/script + ln -s $(pwd)/../../packages/sdk/js ./node_modules/@opencode-ai/sdk + ln -s $(pwd)/../../packages/plugin ./node_modules/@opencode-ai/plugin + + # Use upstream bundle.ts for Nix-compatible bundling + cp ../../nix/bundle.ts ./bundle.ts + chmod +x ./bundle.ts + bun run ./bundle.ts + ) + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + cd packages/opencode + if [ ! -d dist ]; then + echo "ERROR: dist directory missing after bundle step" + exit 1 + fi + + mkdir -p $out/lib/opencode + cp -r dist $out/lib/opencode/ + chmod -R u+w $out/lib/opencode/dist + + # Select bundled worker assets deterministically (sorted find output) + worker_file=$(find "$out/lib/opencode/dist" -type f \( -path '*/tui/worker.*' -o -name 'worker.*' \) | sort | head -n1) + parser_worker_file=$(find "$out/lib/opencode/dist" -type f -name 'parser.worker.*' | sort | head -n1) + if [ -z "$worker_file" ]; then + echo "ERROR: bundled worker not found" + exit 1 + fi + + main_wasm=$(printf '%s\n' "$out"/lib/opencode/dist/tree-sitter-*.wasm | sort | head -n1) + wasm_list=$(find "$out/lib/opencode/dist" -maxdepth 1 -name 'tree-sitter-*.wasm' -print) + for patch_file in "$worker_file" "$parser_worker_file"; do + [ -z "$patch_file" ] && continue + [ ! -f "$patch_file" ] && continue + if [ -n "$wasm_list" ] && grep -q 'tree-sitter' "$patch_file"; then + # Rewrite wasm references to absolute store paths to avoid runtime resolve failures. + bun --bun ../../nix/scripts/patch-wasm.ts "$patch_file" "$main_wasm" $wasm_list + fi + done + + mkdir -p $out/lib/opencode/node_modules + cp -r ../../node_modules/.bun $out/lib/opencode/node_modules/ + mkdir -p $out/lib/opencode/node_modules/@opentui + + # Generate and install JSON schema + mkdir -p $out/share/opencode + HOME=$TMPDIR bun --bun script/schema.ts $out/share/opencode/schema.json + + mkdir -p $out/bin + makeWrapper ${lib.getExe bun} $out/bin/opencode \ + --add-flags "run" \ + --add-flags "$out/lib/opencode/dist/src/index.js" \ + --prefix PATH : ${lib.makeBinPath [fzf ripgrep]} \ + --argv0 opencode + + runHook postInstall + ''; + + postInstall = '' + # Add symlinks for platform-specific native modules + pkgs=( + $out/lib/opencode/node_modules/.bun/@opentui+core-* + $out/lib/opencode/node_modules/.bun/@opentui+solid-* + $out/lib/opencode/node_modules/.bun/@opentui+core@* + $out/lib/opencode/node_modules/.bun/@opentui+solid@* + ) + for pkg in "''${pkgs[@]}"; do + if [ -d "$pkg" ]; then + pkgName=$(basename "$pkg" | sed 's/@opentui+\([^@]*\)@.*/\1/') + ln -sf ../.bun/$(basename "$pkg")/node_modules/@opentui/$pkgName \ + $out/lib/opencode/node_modules/@opentui/$pkgName + fi + done + + ${lib.optionalString + ((stdenvNoCC.buildPlatform.canExecute stdenvNoCC.hostPlatform) + && (stdenvNoCC.hostPlatform.system != "x86_64-darwin")) '' + installShellCompletion --cmd opencode \ + --bash <($out/bin/opencode completion) + ''} + ''; + + passthru = { + jsonschema = "${placeholder "out"}/share/opencode/schema.json"; + tests.version = testers.testVersion { + package = finalAttrs.finalPackage; + command = "HOME=$(mktemp -d) opencode --version"; + inherit (finalAttrs) version; + }; + updateScript = + nix-update-script {extraArgs = ["--subpackage" "node_modules"];}; + }; + + meta = { + description = "AI coding agent built for the terminal"; + longDescription = '' + OpenCode is a terminal-based agent that can build anything. + It combines a TypeScript/JavaScript core with a Go-based TUI + to provide an interactive AI coding experience. + ''; + homepage = "https://github.com/anomalyco/opencode"; + license = lib.licenses.mit; + maintainers = with lib.maintainers; [delafthi]; + sourceProvenance = with lib.sourceTypes; [fromSource]; + platforms = ["aarch64-linux" "x86_64-linux" "aarch64-darwin" "x86_64-darwin"]; + mainProgram = "opencode"; + }; + }) diff --git a/pkgs/opencode/relax-bun-version-check.patch b/pkgs/opencode/relax-bun-version-check.patch new file mode 100644 index 0000000..5d14f16 --- /dev/null +++ b/pkgs/opencode/relax-bun-version-check.patch @@ -0,0 +1,28 @@ +From 0e07ea8225f5667e39c6aa59eea726266f0afab0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= +Date: Thu, 13 Nov 2025 10:16:31 +0100 +Subject: [PATCH] Change Bun version check from error to warning +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Jörg Thalheim +--- + packages/script/src/index.ts | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/packages/script/src/index.ts b/packages/script/src/index.ts +index 141d2b75..de06d0dc 100644 +--- a/packages/script/src/index.ts ++++ b/packages/script/src/index.ts +@@ -10,7 +10,7 @@ if (!expectedBunVersion) { + } + + if (process.versions.bun !== expectedBunVersion) { +- throw new Error(`This script requires bun@${expectedBunVersion}, but you are using bun@${process.versions.bun}`) ++ console.warn(`Warning: This script expects bun@${expectedBunVersion}, but you are using bun@${process.versions.bun}`) + } + + const CHANNEL = process.env["OPENCODE_CHANNEL"] ?? (await $`git branch --show-current`.text().then((x) => x.trim())) +-- +2.51.0