scripts/update-nix.sh and scripts/fix-lockfile.mjs are executed from
the PR's checked-out tree. A fork PR can modify those scripts to run
arbitrary code on the runner. Same-repo PRs and push-to-main still
run; fork PRs skip the job entirely.
Closes#365.
GITHUB_TOKEN no longer pushes anything in this workflow. PR runs are
read-only, and main pushes go through the paseo-ai App token. The
workflow-level 'contents: read' is sufficient.
Addresses #365.
The hash-update commit only changes package-lock.json and
nix/npm-deps.hash. The parent commit already passed all 13 required
checks, and the file diff is content-only, so re-running the full
suite on every hash bump is wasted CI minutes.
Fork PRs cannot read repo secrets, so the App-token step failed with
'Input required and not supplied: app-id'. Gate the App-token step on
push-to-main and fall back to github.token for the checkout on PRs,
since PRs only need to validate the build.
* 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>
* Patch production dependency advisories
* ci: harden dependency installs with npm ci --ignore-scripts
- Replace npm install with npm ci --ignore-scripts across all workflows
to enforce lockfile parity and block dependency postinstall scripts.
- Run npm run postinstall explicitly after install so our root patch
script still applies (the only legitimate postinstall use in this
repo); dep lifecycle scripts stay blocked.
- Add lockfile-lint and npm audit signatures to the lint job to catch
registry-host tampering and verify cryptographic signatures.
- Regenerate package-lock.json to match bumped package.json versions.
* ci: drop --ignore-scripts; rely on npm ci + lockfile-lint + audit signatures
Lavamoat allow-scripts only traverses root deps; it can't see workspace
deps like electron, esbuild, sharp. Without proper monorepo support, an
allowlist would be incomplete and electron's binary download breaks.
Keep the rest of the security stack:
- npm ci (strict lockfile parity)
- lockfile-lint (resolved-host check)
- npm audit signatures (cryptographic verification)
Real script-blocking would need pnpm 10+ migration. Tracking separately.
* ci(nix-build): use PR head SHA so fork checkouts work
The previous `ref: ${{ github.head_ref || github.ref }}` made
actions/checkout fetch a branch name that only exists on the fork,
not origin. Use the PR head SHA instead — origin mirrors PR commits
via refs/pull/N/head, so this works for fork PRs without changing
the push-to-main auto-commit behavior.
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
Single workflow now: update lockfile + hash, build, then commit the fix on
main only if the build passed. Eliminates the structural first-failure where
Nix Build always raced the autofixer and lost.
Apply prettier-compatible formatting across the repo to match the
incoming oxfmt configuration. Mechanical reformat only — no logic
changes. Covers YAML quote normalization, package.json key sorting,
Markdown/TOML formatting, and minor TS whitespace tweaks.
Hash mismatch was hidden by continue-on-error. Now the Nix Build
workflow fails visibly, and fix-nix-hash runs on every push/PR that
touches the lockfile (not just Dependabot).
* fix: add missing resolved/integrity fields to package-lock.json
npm omits resolved URLs and integrity hashes for workspace-local
node_modules overrides. This breaks offline installers like Nix's
npm ci. Add the missing fields for 25 workspace-hoisted packages.
* feat: add Nix flake with package and NixOS module
Add a Nix flake that builds the Paseo daemon (server + CLI) and
provides a NixOS module for declarative deployment.
Package (nix/package.nix):
- Builds relay, server, and CLI workspaces
- Skips onnxruntime-node install script (sandbox-incompatible)
- Rebuilds only node-pty for native terminal support
- Source filter excludes app/website/desktop workspaces
NixOS module (nix/module.nix):
- Systemd service with configurable user, port, listen address
- allowedHosts for DNS rebinding protection
- relay.enable to toggle remote access via app.paseo.sh
- inheritUserEnvironment to expose user tools (git, ssh) to agents
- openFirewall and extra environment variables
ci: add Nix hash maintenance scripts and workflows
scripts/fix-lockfile.mjs:
Adds missing resolved/integrity fields to package-lock.json for
workspace-local overrides. Idempotent, uses `npm view`.
scripts/update-nix.sh:
Runs fix-lockfile.mjs, prefetches deps, computes NAR hash, and
updates npmDepsHash in nix/package.nix. Supports --check for CI.
.github/workflows/nix-build.yml:
Builds the Nix package on push/PR and verifies the lockfile and
hash are up to date.
.github/workflows/fix-nix-hash.yml:
Auto-fixes lockfile signatures and Nix hash on dependabot PRs.
fix: update npmDepsHash after upstream sync
nix: allowlist workspace symlinks instead of blocklist
Prevents build failures when upstream adds new workspace packages.
* don't block PRs on nix failures
* better document npm workaround
* fix hash update script, and update hash
* integrate with npm run build:daemon
* ci: trigger nix build on highlight changes
* fix(nix): update npmDepsHash
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>