From e2533f5e371cf1475bbc69e5ed10e5691d8cd62c Mon Sep 17 00:00:00 2001 From: p0nczek Date: Sat, 11 Jul 2026 21:42:04 +0200 Subject: [PATCH 1/3] fix(nixos): dev environment support via flake + preload/network fixes - Add flake.nix devShell: gcc/make/python3 for node-pty, electron binary linked from nixpkgs, ELECTRON_OZONE_PLATFORM_HINT=x11 for Niri/Wayland - electron/preload.ts: assign window.kadr unconditionally so Vite can't tree-shake the api object when contextIsolation is statically false - electron/main.ts: delay initial loadURL and normalize to 127.0.0.1 to avoid an intermittent ERR_NETWORK_CHANGED - electron.vite.config.ts: pin renderer dev server to host 127.0.0.1 - docs/nixos.md: setup instructions with and without the flake --- docs/nixos.md | 110 ++++++++++++++++++++++++++++++++++++++++ electron.vite.config.ts | 4 ++ electron/main.ts | 13 +++-- electron/preload.ts | 7 ++- flake.nix | 53 +++++++++++++++++++ 5 files changed, 181 insertions(+), 6 deletions(-) create mode 100644 docs/nixos.md create mode 100644 flake.nix diff --git a/docs/nixos.md b/docs/nixos.md new file mode 100644 index 0000000..97725be --- /dev/null +++ b/docs/nixos.md @@ -0,0 +1,110 @@ +# Running Kadr on NixOS / Запуск Kadr на NixOS + +
+English + +## Quick start (with flake) + +```bash +nix develop +npm install +npm run dev +``` + +The devShell provides `gcc`/`make`/`python3` (needed to build the native +`node-pty` dependency), `electron`, and `ffmpeg`, and automatically: + +- forces X11/XWayland via `ELECTRON_OZONE_PLATFORM_HINT=x11` (native + Wayland/Ozone can crash or render a blank window depending on your + compositor), +- links `node_modules/electron/dist/electron` to the nixpkgs Electron + binary, since the npm `electron` package's binary download doesn't work + on NixOS's non-FHS filesystem. + +## Without the flake + +If you don't want to use flakes, run once per shell session: + +```bash +nix-shell -p gcc gnumake python3 --run "npm install" + +export ELECTRON_OZONE_PLATFORM_HINT=x11 +export XDG_SESSION_TYPE=x11 +unset WAYLAND_DISPLAY + +nix-shell -p electron --run ' + mkdir -p node_modules/electron/dist + printf "electron" > node_modules/electron/path.txt + ln -sf $(which electron) node_modules/electron/dist/electron + npm run dev +' +``` + +## Known-good config + +These fixes live in the app code (not NixOS-specific), but are worth +knowing about if you hit similar symptoms elsewhere: + +- `electron.vite.config.ts` pins the renderer dev server to + `host: '127.0.0.1'` to avoid an intermittent `ERR_NETWORK_CHANGED` on + systems with IPv6 disabled or misconfigured. +- `electron/preload.ts` assigns `window.kadr` unconditionally (guarded by + `Object.keys(api)`) so Vite's bundler can't tree-shake the whole API + object away when `contextIsolation: false` is a compile-time constant. + +
+ +
+Русский + +## Быстрый старт (с флейком) + +```bash +nix develop +npm install +npm run dev +``` + +DevShell предоставляет `gcc`/`make`/`python3` (нужны для сборки нативной +зависимости `node-pty`), `electron` и `ffmpeg`, а также автоматически: + +- принудительно включает X11/XWayland через `ELECTRON_OZONE_PLATFORM_HINT=x11` + (нативный Wayland/Ozone может падать или рисовать пустое окно в + зависимости от вашего компоузера), +- линкует `node_modules/electron/dist/electron` на бинарник Electron из + nixpkgs, так как загрузка бинарника npm-пакетом `electron` не работает + на не-FHS файловой системе NixOS. + +## Без флейка + +Если не хотите использовать флейки, выполняйте один раз на сессию шелла: + +```bash +nix-shell -p gcc gnumake python3 --run "npm install" + +export ELECTRON_OZONE_PLATFORM_HINT=x11 +export XDG_SESSION_TYPE=x11 +unset WAYLAND_DISPLAY + +nix-shell -p electron --run ' + mkdir -p node_modules/electron/dist + printf "electron" > node_modules/electron/path.txt + ln -sf $(which electron) node_modules/electron/dist/electron + npm run dev +' +``` + +## Известные фиксы в конфиге + +Эти фиксы находятся в коде приложения (не специфичны для NixOS), но +полезно о них знать при похожих симптомах в других окружениях: + +- `electron.vite.config.ts` закрепляет dev-сервер рендерера на + `host: '127.0.0.1'`, что убирает периодический `ERR_NETWORK_CHANGED` на + системах с отключённым или неправильно настроенным IPv6. +- `electron/preload.ts` присваивает `window.kadr` безусловно (под защитой + `Object.keys(api)`), чтобы бандлер Vite не мог вытряхнуть весь объект + API tree-shaking'ом, когда `contextIsolation: false` известна на этапе + компиляции. + +
diff --git a/electron.vite.config.ts b/electron.vite.config.ts index f32c57f..7e80a3d 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -26,6 +26,10 @@ export default defineConfig({ }, renderer: { root: '.', + server: { + host: '127.0.0.1', + port: 5173 + }, build: { outDir: 'out/renderer', rollupOptions: { input: resolve(__dirname, 'index.html') } diff --git a/electron/main.ts b/electron/main.ts index a6d2b34..f6d6302 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -88,11 +88,14 @@ function createWindow() { app.exit(1) } }) - if (process.env.ELECTRON_RENDERER_URL) { - win.loadURL(process.env.ELECTRON_RENDERER_URL) - } else { - win.loadFile(join(__dirname, '../renderer/index.html')) - } + setTimeout(() => { + if (process.env.ELECTRON_RENDERER_URL) { + const url = process.env.ELECTRON_RENDERER_URL.replace('localhost', '127.0.0.1'); + win.loadURL(url) + } else { + win.loadFile(join(__dirname, '../renderer/index.html')) + } + }, 1500); } /** diff --git a/electron/preload.ts b/electron/preload.ts index c009d4d..727c747 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -156,8 +156,13 @@ const api: KadrApi = { } } +// Side-effect guard: prevent Vite from tree-shaking the api object +// because the renderer accesses these methods at runtime via window.kadr +void Object.keys(api) + // contextIsolation is off (see main.ts: export frames pass by reference), // so the api object lands on the shared window directly; the bridge branch // keeps working if isolation is ever re-enabled. +Object.defineProperty(globalThis, 'kadr', { value: api, writable: true, configurable: true }) if (process.contextIsolated) contextBridge.exposeInMainWorld('kadr', api) -else (globalThis as unknown as { kadr: KadrApi }).kadr = api +console.log('[preload] kadr api keys:', Object.keys(api)) diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..45648b0 --- /dev/null +++ b/flake.nix @@ -0,0 +1,53 @@ +{ + description = "Kadr dev environment (NixOS)"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + in + { + devShells.default = pkgs.mkShell { + buildInputs = [ + pkgs.nodejs_22 + pkgs.electron + pkgs.ffmpeg + pkgs.gcc + pkgs.gnumake + pkgs.python3 + pkgs.pkg-config + ]; + + shellHook = '' + # --- Force X11/XWayland: native Wayland/Ozone crashes or renders + # a blank window under some compositors (observed on Niri). --- + export ELECTRON_OZONE_PLATFORM_HINT=x11 + export XDG_SESSION_TYPE=x11 + unset WAYLAND_DISPLAY + unset NIRI_SOCKET + + export KADR_FFMPEG="${pkgs.ffmpeg}/bin/ffmpeg" + export KADR_FFPROBE="${pkgs.ffmpeg}/bin/ffprobe" + + # --- npm's "electron" package expects to download a prebuilt + # binary into node_modules/electron/dist/, which fails on + # NixOS's non-FHS filesystem. Point it at the nixpkgs build + # instead, the same way `electron` upstream supports via + # path.txt + dist/. --- + if [ -d node_modules/electron ] && [ ! -f node_modules/electron/dist/electron ]; then + mkdir -p node_modules/electron/dist + printf "electron" > node_modules/electron/path.txt + ln -sf "${pkgs.electron}/bin/electron" node_modules/electron/dist/electron + echo "[flake] linked node_modules/electron/dist/electron -> nixpkgs electron" + fi + + echo "[flake] Kadr devShell ready. Run: npm install && npm run dev" + ''; + }; + }); +} From a7ab0c8e767ad1c1562039e1ed09ebe8ef5b3c23 Mon Sep 17 00:00:00 2001 From: p0nczek Date: Sat, 11 Jul 2026 22:05:59 +0200 Subject: [PATCH 2/3] docs: add NixOS section to README --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 245a3a6..a24d1ee 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,21 @@ Claude/npm нужен прокси — создайте `~/.config/kadr/claude-e { "env": { "HTTPS_PROXY": "http://127.0.0.1:1080", "NO_PROXY": "127.0.0.1,localhost" } } ``` +## NixOS + +На NixOS (проверено на Niri/Wayland) `npm install`/`npm run dev` из коробки +может упасть или показать пустой экран из-за нескольких платформенных +особенностей (сборка `node-pty`, отсутствие prebuilt-бинарника Electron, +Wayland/Ozone). Есть `flake.nix` с готовым `devShell`: + +```bash +nix develop +npm install +npm run dev +``` + +Подробности и вариант без флейков — в [docs/nixos.md](docs/nixos.md). + ## Как устроена ИИ-интеграция Kadr поднимает локальный мост в рендерер и отдаёт Claude MCP-сервер с From 69b830b58839c26b5329eb529529b7b28b15dea8 Mon Sep 17 00:00:00 2001 From: p0nczek Date: Sat, 11 Jul 2026 22:06:58 +0200 Subject: [PATCH 3/3] docs: add NixOS section to README.en --- README.en.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.en.md b/README.en.md index 8a58512..c561b0c 100644 --- a/README.en.md +++ b/README.en.md @@ -104,6 +104,21 @@ proxy for Claude/npm, create `~/.config/kadr/claude-env.json`: { "env": { "HTTPS_PROXY": "http://127.0.0.1:1080", "NO_PROXY": "127.0.0.1,localhost" } } ``` +## NixOS + +On NixOS (tested on Niri/Wayland), a plain `npm install`/`npm run dev` can +fail or render a blank window because of a few platform quirks (building +`node-pty`, no prebuilt Electron binary, Wayland/Ozone). A ready-made +`flake.nix` devShell handles this: + +```bash +nix develop +npm install +npm run dev +``` + +See [docs/nixos.md](docs/nixos.md) for details and a non-flake fallback. + ## How the AI integration works Kadr starts a local HTTP bridge into the renderer and hands Claude an MCP