mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
* nix: expose npmDepsHash as a callPackage arg
Downstream flakes that follow a different nixpkgs revision can hit a
hash mismatch on the npm-deps FOD even though package-lock.json is
unchanged, because fetchNpmDeps output is sensitive to nixpkgs version.
The standard fix — `.overrideAttrs { npmDepsHash = ...; }` — does not
work for buildNpmPackage: npmDepsHash is destructured from args, so the
default `npmDeps = fetchNpmDeps { hash = npmDepsHash; }` is already
bound by the time overrideAttrs runs.
Promote npmDepsHash to a callPackage arg with the current value as the
default. Consumers can now `.override { npmDepsHash = "sha256-..."; }`
and have it propagate to the npmDeps fetcher. Upstream CI behavior is
unchanged — update-nix.sh is adjusted to match the new
`npmDepsHash ? "..."` pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* nix: move npmDepsHash default to a sidecar file
Read the default `npmDepsHash` from `nix/npm-deps.hash` via
`lib.fileContents` instead of inlining it as a string literal in
`nix/package.nix`. The CI auto-updater becomes a one-line file write
instead of a regex against a .nix source — decoupling lockfile bumps
from the formatting of the package definition.
No behavior change: same hash, same default, same `.override` surface.
Lockfile diffs become smaller and the update path stops being load-
bearing on a sed pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* nix: declarative config via services.paseo.settings
Today only a handful of `config.json` fields are surfaced as module
options (listen, port, hostnames, relay.enable). Anything richer —
custom agent providers, MCP injection, log config, voice features —
requires hand-editing `$PASEO_HOME/config.json`.
Add `services.paseo.settings` as a freeform attrset rendered to JSON
via `pkgs.formats.json` and installed at `$PASEO_HOME/config.json`
on each service start. Standard NixOS idiom.
`install` on `preStart` rather than a `tmpfiles` symlink because the
daemon writes to `config.json` at runtime via `DaemonConfigStore.patch`
(MCP / provider toggles). A read-only symlink would break those writes;
a copy-on-start lets the daemon mutate freely within a session while
the Nix-managed file remains the source of truth at boot.
The full schema is `PersistedConfigSchema` in
`packages/server/src/server/persisted-config.ts`. Documented in the
option description that runtime mutations don't survive restarts when
`settings` is non-empty.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* nix: typed services.paseo.relay options with auto-wired endpoint
Addresses #224 (option surface only).
Today `services.paseo.relay.enable` is a bool that just toggles
`--no-relay`. Pointing the daemon at a self-hosted relay requires
hand-setting `PASEO_RELAY_ENDPOINT` and `PASEO_RELAY_USE_TLS` via
the freeform `environment` option.
Add a typed relay subtree:
- `relay.mode = "hosted" | "remote"` selects how the daemon reaches
the relay when enabled. Default is `"hosted"` (current behavior).
- `relay.{host,port,useTls}` configure the `"remote"` case.
- The module auto-wires `PASEO_RELAY_ENDPOINT` and `PASEO_RELAY_USE_TLS`
when `mode = "remote"`.
- Assertion fires at eval time when `mode = "remote"` but `host` is empty.
- `relay.enable` keeps its current semantics — bool answers "is it on?",
the new options answer "how is it configured?".
The `"local"` mode from #224 (running a relay on the same host as a
systemd unit) is deliberately not added here: `packages/relay` ships
only a Cloudflare Workers adapter, so there's no Node.js runtime to
package as a binary. Adding a Node adapter is a TS-side feature change
worth its own design discussion; tracked as a follow-up.
No breaking changes — existing `relay.enable = true|false` configs
evaluate unchanged with the new `mode = "hosted"` default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* nix: package paseo desktop app for Linux
NixOS users have no easy way to run the desktop app today —
electron-builder's outputs (.deb, .rpm, .AppImage) don't fit Nix's
model, so `nix run github:getpaseo/paseo#desktop` doesn't exist.
Add `packages.<linux>.desktop` following the standard nixpkgs Electron
pattern (see e.g. signal-desktop, vscode): skip electron-builder
entirely, build the desktop main process with `tsc`, bundle the Expo
web export and built daemon workspaces, and wrap `pkgs.electron` with
`makeWrapper`. Output is a runnable derivation usable via `nix run` or
`environment.systemPackages`.
The install layout preserves the monorepo source tree
(`packages/desktop/dist/main.js`, `packages/app/dist`, `node_modules`
at the workspace root) so `main.ts`'s dev-mode path resolution
(`__dirname/../../app/dist`, `__dirname/../assets/icon.png`) works
without any source patches. When Electron is invoked unpackaged via
`electron path/to/main.js`, `app.isPackaged` is false and these
relative paths are used.
`--no-sandbox` is set on the launcher: Chromium's setuid sandbox can't
live in `/nix/store` (immutable, no setuid). A follow-up can wire
`security.wrappers` from a NixOS module for users who want the
renderer sandbox.
No CI changes — `desktop-release.yml` continues to produce
.deb/.AppImage/.rpm/macOS/Windows installers as today. This is purely
additive for NixOS users.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* nix: copy full packages/ tree in desktop derivation
The previous installPhase selectively copied built artifacts (dist/
under server, cli, relay, highlight, expo-two-way-audio), which left
two workspace symlinks dangling and failed noBrokenSymlinks:
- node_modules/@getpaseo/expo-two-way-audio → packages/expo-two-way-audio
(the Expo native module ships source + native projects, no built dist/)
- node_modules/.bin/paseo → @getpaseo/cli/bin/paseo
(the CLI launcher script lives under bin/, not dist/)
npm workspace symlinks expect every workspace package to exist at its
source path. Copy the whole packages/ tree instead. The cleanSourceWith
filter already excludes the heavy platform-specific paths (android/ios
under packages/app, website, tests), and the remaining ~16MB of src is
acceptable for an Electron app derivation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* nix: route desktop renderer through paseo:// protocol handler
When `paseo-desktop` is launched via `electron path/to/main.js` (our
unpackaged Nix layout), `app.isPackaged` is false and main.ts loads
`DEV_SERVER_URL` — which defaults to http://localhost:8081 (the Expo
dev server). That URL has nothing listening in a Nix-installed run,
so the renderer fails with ERR_CONNECTION_REFUSED.
main.ts already supports overriding this via the `EXPO_DEV_URL` env
var. Set it to `paseo://app/` so the request goes through the
`paseo://` protocol handler that main.ts registers unconditionally.
The handler resolves files via `getAppDistDir()`, which in the
unpackaged branch returns `__dirname/../../app/dist` — exactly where
our install layout places the Expo web export.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: track nix/npm-deps.hash in nix-build commit step
The commit step still referenced nix/package.nix in its diff check
and git add. After moving the hash to nix/npm-deps.hash, the
auto-updated hash would never be staged and the new value would
sit unstaged in the working tree forever.
* ci: push nix-build hash commits via paseo-ai[bot] App token
The default GITHUB_TOKEN cannot bypass main's required status checks,
so the auto-commit of stale Nix hash updates has been silently failing.
Mint an installation token for the paseo-ai App (which is in the
ruleset bypass list) and use it for checkout and push.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
154 lines
5.2 KiB
Nix
154 lines
5.2 KiB
Nix
{
|
|
lib,
|
|
stdenv,
|
|
buildNpmPackage,
|
|
nodejs_22,
|
|
python3,
|
|
makeWrapper,
|
|
# node-pty needs libuv headers on Linux
|
|
libuv,
|
|
# Exposed so downstream flakes that follow a different nixpkgs revision
|
|
# (where `fetchNpmDeps` may produce a different hash for the same lockfile)
|
|
# can override via `.override { npmDepsHash = "sha256-..."; }` without
|
|
# `overrideAttrs` gymnastics — `npmDepsHash` is destructured from
|
|
# `buildNpmPackage`'s args, so `overrideAttrs` cannot reach it.
|
|
#
|
|
# The default is read from a sidecar file so the CI auto-updater can replace
|
|
# the hash with a single file write instead of a sed against this source.
|
|
npmDepsHash ? lib.fileContents ./npm-deps.hash,
|
|
}:
|
|
|
|
buildNpmPackage rec {
|
|
pname = "paseo";
|
|
version = (builtins.fromJSON (builtins.readFile ../package.json)).version;
|
|
|
|
src = lib.cleanSourceWith {
|
|
src = ./..;
|
|
filter = path: type:
|
|
let
|
|
baseName = builtins.baseNameOf path;
|
|
relPath = lib.removePrefix (toString ./..) path;
|
|
in
|
|
# Exclude non-daemon workspace contents (keep package.json for workspace resolution)
|
|
!(lib.hasPrefix "/packages/app/src" relPath)
|
|
&& !(lib.hasPrefix "/packages/app/assets" relPath)
|
|
&& !(lib.hasPrefix "/packages/app/android" relPath)
|
|
&& !(lib.hasPrefix "/packages/app/ios" relPath)
|
|
&& !(lib.hasPrefix "/packages/website/src" relPath)
|
|
&& !(lib.hasPrefix "/packages/website/public" relPath)
|
|
&& !(lib.hasPrefix "/packages/desktop/src" relPath)
|
|
&& !(lib.hasPrefix "/packages/desktop/src-tauri" relPath)
|
|
# Exclude test fixtures and debug files
|
|
&& !(lib.hasSuffix ".test.ts" baseName)
|
|
&& !(lib.hasSuffix ".e2e.test.ts" baseName)
|
|
&& baseName != "node_modules"
|
|
&& baseName != ".git"
|
|
&& baseName != ".paseo"
|
|
&& baseName != ".DS_Store";
|
|
};
|
|
|
|
nodejs = nodejs_22;
|
|
|
|
# Default hash lives in nix/npm-deps.hash (see arg default above).
|
|
# CI auto-updates that file when package-lock.json changes (see .github/workflows/).
|
|
inherit npmDepsHash;
|
|
|
|
# Prevent onnxruntime-node's install script from running during automatic
|
|
# npm rebuild (it tries to download from api.nuget.org, which fails in the sandbox).
|
|
# We manually rebuild only node-pty in buildPhase.
|
|
npmRebuildFlags = [ "--ignore-scripts" ];
|
|
|
|
nativeBuildInputs = [
|
|
python3 # for node-gyp (node-pty compilation)
|
|
makeWrapper
|
|
];
|
|
|
|
buildInputs = lib.optionals stdenv.hostPlatform.isLinux [
|
|
libuv
|
|
];
|
|
|
|
# Don't use the default npm build hook — we need a custom build sequence
|
|
dontNpmBuild = true;
|
|
|
|
buildPhase = ''
|
|
runHook preBuild
|
|
|
|
# Rebuild only node-pty (native addon for terminal emulation).
|
|
# Speech-related native modules (sherpa-onnx, onnxruntime-node) are
|
|
# intentionally left unbuilt — they're lazily loaded and gracefully
|
|
# degrade when unavailable.
|
|
npm rebuild node-pty
|
|
|
|
# Build all daemon packages in dependency order (defined in package.json)
|
|
npm run build:daemon
|
|
|
|
runHook postBuild
|
|
'';
|
|
|
|
installPhase = ''
|
|
runHook preInstall
|
|
|
|
mkdir -p $out/lib/paseo
|
|
|
|
# Copy root package metadata
|
|
cp package.json $out/lib/paseo/
|
|
|
|
# Copy node_modules (preserving workspace symlinks)
|
|
cp -a node_modules $out/lib/paseo/
|
|
|
|
# Auto-detect which @getpaseo/* packages were built by build:daemon
|
|
# (they'll have a dist/ directory). Copy those and remove the rest.
|
|
for link in $out/lib/paseo/node_modules/@getpaseo/*; do
|
|
name=$(basename "$link")
|
|
if [ -d "packages/$name/dist" ]; then
|
|
mkdir -p "$out/lib/paseo/packages/$name"
|
|
cp "packages/$name/package.json" "$out/lib/paseo/packages/$name/"
|
|
cp -a "packages/$name/dist" "$out/lib/paseo/packages/$name/"
|
|
if [ -d "packages/$name/node_modules" ]; then
|
|
cp -a "packages/$name/node_modules" "$out/lib/paseo/packages/$name/"
|
|
fi
|
|
else
|
|
rm -f "$link"
|
|
fi
|
|
done
|
|
|
|
# Copy CLI bin entry
|
|
mkdir -p $out/lib/paseo/packages/cli/bin
|
|
cp packages/cli/bin/paseo $out/lib/paseo/packages/cli/bin/
|
|
|
|
# Copy extra server files referenced at runtime
|
|
for f in agent-prompt.md .env.example; do
|
|
if [ -f packages/server/$f ]; then
|
|
cp packages/server/$f $out/lib/paseo/packages/server/
|
|
fi
|
|
done
|
|
|
|
# Copy server scripts (including supervisor-entrypoint) needed by CLI
|
|
if [ -d packages/server/dist/scripts ]; then
|
|
mkdir -p $out/lib/paseo/packages/server/dist/scripts
|
|
cp -a packages/server/dist/scripts/* $out/lib/paseo/packages/server/dist/scripts/
|
|
fi
|
|
|
|
# Create wrapper for the server entry point (for systemd / direct use)
|
|
mkdir -p $out/bin
|
|
makeWrapper ${nodejs}/bin/node $out/bin/paseo-server \
|
|
--add-flags "$out/lib/paseo/packages/server/dist/scripts/supervisor-entrypoint.js" \
|
|
--set NODE_ENV production
|
|
|
|
# Create wrapper for the CLI
|
|
makeWrapper ${nodejs}/bin/node $out/bin/paseo \
|
|
--add-flags "$out/lib/paseo/packages/cli/dist/index.js" \
|
|
--set NODE_PATH "$out/lib/paseo/node_modules"
|
|
|
|
runHook postInstall
|
|
'';
|
|
|
|
meta = {
|
|
description = "Self-hosted daemon for Claude Code, Codex, and OpenCode";
|
|
homepage = "https://github.com/getpaseo/paseo";
|
|
license = lib.licenses.agpl3Plus;
|
|
mainProgram = "paseo";
|
|
platforms = lib.platforms.linux ++ lib.platforms.darwin;
|
|
};
|
|
}
|